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 021ca4ea26a2 Camel TUI: Make popups own the footer on every tab and 
confirm process kill/stop
021ca4ea26a2 is described below

commit 021ca4ea26a2e3da3e56fa2ab9d5f68cf4d22d2e
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Sep 8 09:20:44 2026 +0200

    Camel TUI: Make popups own the footer on every tab and confirm process 
kill/stop
    
    - Popups (Actions menu and its sub-dialogs, F10 process control, confirm
      dialogs) now replace the bottom hint bar regardless of the active tab.
      Previously this only happened on the Overview tab, so on Source and
      other tabs the tab's own hints stayed visible underneath the popup.
    - Stop, Restart and Kill in the F10 menu now honor the Confirm Actions
      setting and ask before terminating a process, like Quit and Stop All.
    - Confirm dialogs share one key contract: Enter accepts, Esc cancels and
      any other key is swallowed instead of silently dismissing the dialog.
    - The confirm dialogs and the editor's discard prompt now contribute
      footer hints; the duplicated "F10 run" hint on Overview is removed.
    
    Camel TUI: Add a shared dialog kit so confirm and input dialogs look the 
same
    
    Introduce DialogHelper with one confirm-dialog renderer, one single-field
    input-dialog renderer and one placement formula (centered, upper third),
    plus TuiHelper.hintLine/hintTitle for footer-style key badges inside a
    dialog and FormHelper.renderTextField for the caret-painting text field.
    
    Migrated to the kit:
    - Quit/Kill/generic confirm (PopupManager), Delete file (FileActionsPopup)
      and Discard changes (SourceViewer) now share the same box, accent color
      (warning, or error for irreversible actions) and badge-style key hints
      instead of hand-built bold text.
    - Rename/new file and Refactor input dialogs use the shared input dialog.
    - Settings, Run options, Open Project and Infra port dialogs use the shared
      text field instead of seven copy-pasted TextInput blocks; the run form's
      error line uses the error color.
    - Help, Options viewer and Doc viewer build their bottom-border hints from
      the same spans as their footer, so the two can no longer disagree.
    - Open Project, Run options and AI Provider dialogs use the shared
      placement instead of magic offsets.
    - AI Provider popup now contributes footer hints; trailing double spaces
      in the Files browser footer and a broken key badge in the AI log fixed.
    
    Camel TUI: Settle the key-hint vocabulary across popups
    
    One rule set for the hints a popup shows:
    - Lists and menus: "↑↓ navigate" (the shared icon constant, no more
      literal arrows or "Up/Down select") then "Enter" with its verb.
    - Esc is "cancel" when pending input or a preview would be discarded
      (confirm dialogs, text entry, Settings, Theme, Open Project),
      "close" for menus, pickers and viewers (Actions, F10 run menu, Doctor,
      AI/MCP log, doc picker, Send Message, AI provider, infra browser) and
      "back" only when Esc returns to a parent popup (submenus, run form,
      example category, rename input).
    
    The SQL row editor's in-popup "F5=Save Esc=Cancel" line now uses the
    shared badge-style hint line like every other dialog.
    
    Camel TUI: Show viewer key hints in the footer bar only
    
    The Help, Doc, Options and AI log viewers drew their key hints both in
    the popup's bottom border and in the footer bar. Now that both use the
    same badge style the repetition is obvious, so the in-border copy is
    removed and the footer bar remains the single place for hints.
    
    Camel TUI: Drop the implied "↑↓ navigate" and "↑↓ scroll" footer hints
    
    Arrow keys moving a list or scrolling a viewer is the default expectation
    in the TUI, so the badge only cost footer width (which matters because
    the footer drops F-key hints when it overflows). Removed from all 57
    footers that showed it. The arrow badge stays only where it does
    something a user would not guess: "step through path" in the trace views,
    "history" in the Open Project and HTTP probe fields, "preview" in the
    Theme picker, and "select" next to "PgUp/Dn detail" in the two-pane
    AI and MCP log popups.
    
    Camel TUI: Size the Doctor popup to its content so detail rows are not 
clipped
    
    The popup had a fixed width of 62 columns, which cut off the second
    line of the AI and Ollama checks (e.g. "Set ANTHROPIC_API_KEY, 
AZURE_OPENAI_*, G").
    It is now as wide as its longest line plus a right margin, clamped to
    the screen.
    
    Camel TUI: Word-wrap Doctor detail rows instead of widening the popup
    
    Keep the Doctor popup at a fixed comfortable width and wrap the dimmed
    detail rows under a check on word boundaries, so long hints such as the
    list of AI API key variables neither get clipped nor stretch the popup
    across the screen. Error messages from Maven resolution are wrapped in
    full instead of being truncated to 40 characters.
    
    Camel TUI: Make the Setup AI page a real guide to configuring the AI panel
    
    The F2 -> AI & MCP -> Setup AI page only described how to connect an
    MCP client, which MCP Info already covers. It now mirrors the AI section
    of the TUI user manual: which provider is currently detected, the
    environment variables and detection order, pinning a provider in
    Settings, running Ollama locally with the recommended tool-calling
    models, OpenAI-compatible servers, the AI panel keys and slash commands,
    and a short pointer to MCP for driving the TUI from a coding agent.
    
    The user manual now cross-references the in-TUI page and the Settings
    keys.
    
    Co-Authored-By: Claude Fable 5.1 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../modules/ROOT/pages/camel-jbang-tui.adoc        |   4 +
 .../dsl/jbang/core/commands/tui/ActionsPopup.java  |   4 +-
 .../dsl/jbang/core/commands/tui/ActivityTab.java   |   1 -
 .../dsl/jbang/core/commands/tui/AiLogPopup.java    |   5 +-
 .../camel/dsl/jbang/core/commands/tui/AiPanel.java |   4 +
 .../core/commands/tui/AiProviderSwitchPopup.java   |   9 +-
 .../dsl/jbang/core/commands/tui/BeansTab.java      |   1 -
 .../dsl/jbang/core/commands/tui/BrowseTab.java     |   1 -
 .../dsl/jbang/core/commands/tui/CamelMonitor.java  |  34 +++--
 .../dsl/jbang/core/commands/tui/CatalogTab.java    |   1 -
 .../jbang/core/commands/tui/CircuitBreakerTab.java |   1 -
 .../dsl/jbang/core/commands/tui/ClasspathTab.java  |   1 -
 .../jbang/core/commands/tui/ConfigurationTab.java  |   1 -
 .../dsl/jbang/core/commands/tui/CveAuditTab.java   |   1 -
 .../dsl/jbang/core/commands/tui/DialogHelper.java  | 158 +++++++++++++++++++++
 .../jbang/core/commands/tui/DocViewerPopup.java    | 119 ++++++++++------
 .../dsl/jbang/core/commands/tui/DoctorPopup.java   |  62 +++++---
 .../dsl/jbang/core/commands/tui/EndpointsTab.java  |   2 -
 .../dsl/jbang/core/commands/tui/ErrorsTab.java     |   1 -
 .../core/commands/tui/ExampleBrowserPopup.java     |   6 +-
 .../jbang/core/commands/tui/FileActionsPopup.java  |  77 ++--------
 .../dsl/jbang/core/commands/tui/FilesBrowser.java  |   3 +-
 .../dsl/jbang/core/commands/tui/FolderBrowser.java |   1 -
 .../jbang/core/commands/tui/FolderInputPopup.java  |  34 ++---
 .../dsl/jbang/core/commands/tui/FormHelper.java    |  29 ++++
 .../dsl/jbang/core/commands/tui/HelpOverlay.java   |  10 +-
 .../dsl/jbang/core/commands/tui/HistoryTab.java    |   3 -
 .../camel/dsl/jbang/core/commands/tui/HttpTab.java |   1 -
 .../jbang/core/commands/tui/InfraBrowserPopup.java |  20 +--
 .../camel/dsl/jbang/core/commands/tui/LogTab.java  |   2 -
 .../core/commands/tui/MavenDependenciesTab.java    |   1 -
 .../dsl/jbang/core/commands/tui/McpFacade.java     |   7 +-
 .../dsl/jbang/core/commands/tui/McpLogPopup.java   |   2 +-
 .../dsl/jbang/core/commands/tui/MetricsTab.java    |   1 -
 .../dsl/jbang/core/commands/tui/NetworkTab.java    |   1 -
 .../core/commands/tui/OptionsViewerPopup.java      |  17 ---
 .../dsl/jbang/core/commands/tui/OverviewTab.java   |   5 +-
 .../dsl/jbang/core/commands/tui/PopupManager.java  |  72 ++--------
 .../core/commands/tui/ProcessControlPopup.java     |  38 ++++-
 .../dsl/jbang/core/commands/tui/ProcessTab.java    |   1 -
 .../dsl/jbang/core/commands/tui/RefactorPopup.java |  30 +---
 .../dsl/jbang/core/commands/tui/RoutesTab.java     |   1 -
 .../jbang/core/commands/tui/RunOptionsForm.java    |  45 ++----
 .../jbang/core/commands/tui/SendMessagePopup.java  |   2 +-
 .../dsl/jbang/core/commands/tui/SettingsPopup.java |  20 +--
 .../dsl/jbang/core/commands/tui/SourceTab.java     |   1 -
 .../dsl/jbang/core/commands/tui/SourceViewer.java  |  41 ++----
 .../dsl/jbang/core/commands/tui/SpansTab.java      |   2 -
 .../dsl/jbang/core/commands/tui/SqlQueryTab.java   |  10 +-
 .../dsl/jbang/core/commands/tui/SqlTraceTab.java   |   1 -
 .../dsl/jbang/core/commands/tui/StartupTab.java    |   1 -
 .../dsl/jbang/core/commands/tui/ThemePopup.java    |   2 +-
 .../dsl/jbang/core/commands/tui/ThreadsTab.java    |   1 -
 .../jbang/core/commands/tui/TransformersTab.java   |   1 -
 .../dsl/jbang/core/commands/tui/TuiHelper.java     |  21 +++
 .../jbang/core/commands/tui/TypeConvertersTab.java |   1 -
 .../core/commands/tui/BeansTabRenderTest.java      |   3 +-
 .../jbang/core/commands/tui/DialogHelperTest.java  | 118 +++++++++++++++
 .../core/commands/tui/DoctorPopupWrapTest.java     |  57 ++++++++
 .../core/commands/tui/OverviewTabRenderTest.java   |   1 -
 .../jbang/core/commands/tui/PopupManagerTest.java  |  45 ++++++
 .../core/commands/tui/ProcessControlPopupTest.java | 158 +++++++++++++++++++++
 .../core/commands/tui/ProcessTabRenderTest.java    |   1 -
 63 files changed, 850 insertions(+), 454 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 174c298f9810..7acf42b6c811 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-tui.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-tui.adoc
@@ -752,6 +752,10 @@ Press *F8* to open the built-in AI prompt panel. The panel 
auto-detects a provid
 
 Press *Ctrl+P* inside the AI panel to switch provider or model at any time.
 
+The same guidance, together with the provider that is currently detected, is 
available inside the TUI
+via *F2* -> _AI & MCP_ -> _Setup AI_. Use *F2* -> _Settings_ to pin a 
provider, model or base URL
+(`camel.tui.ai.provider`, `camel.tui.ai.model`, `camel.tui.ai.url`) regardless 
of the environment.
+
 ==== Using Ollama (local, no API key)
 
 Install Ollama natively for best performance — the native binary uses GPU 
acceleration
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 403865fae1dd..a0b94aa5b9fd 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
@@ -929,15 +929,13 @@ class ActionsPopup {
         }
         if (gotoTabPopup.isVisible()) {
             hint(spans, "type", "filter");
-            hint(spans, TuiIcons.HINT_SCROLL, "navigate");
             hint(spans, "Enter", "go to");
             hintLast(spans, "Esc", "back");
             return;
         }
         if (showActionsMenu) {
-            hint(spans, TuiIcons.HINT_SCROLL, "navigate");
             hint(spans, "Enter", "select");
-            hintLast(spans, "Esc", currentSubmenu != null ? "back" : "cancel");
+            hintLast(spans, "Esc", currentSubmenu != null ? "back" : "close");
         }
     }
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActivityTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActivityTab.java
index 52bc50b324c8..d691fa9413f3 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActivityTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActivityTab.java
@@ -289,7 +289,6 @@ class ActivityTab extends AbstractTableTab {
     @Override
     public void renderFooter(List<Span> spans) {
         TuiHelper.hint(spans, "Esc", "back");
-        TuiHelper.hint(spans, TuiIcons.HINT_SCROLL, "navigate");
         TuiHelper.hint(spans, "PgUp/Dn", "detail");
         if (!wordWrap) {
             TuiHelper.hint(spans, TuiIcons.HINT_H, "h-scroll");
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AiLogPopup.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AiLogPopup.java
index 90cb734186d0..575f3280b900 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AiLogPopup.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AiLogPopup.java
@@ -32,7 +32,6 @@ import dev.tamboui.widgets.Clear;
 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.list.ListItem;
 import dev.tamboui.widgets.list.ListState;
 import dev.tamboui.widgets.list.ListWidget;
@@ -102,8 +101,6 @@ class AiLogPopup {
             Block block = Block.builder()
                     .borderType(BorderType.ROUNDED).borders(Borders.ALL)
                     .title(" AI Log ")
-                    .titleBottom(Title.from(Line.from(
-                            Span.styled(" Esc", Theme.hintKey()), Span.raw(" 
back "))))
                     .build();
             frame.renderWidget(block, popup);
             Rect inner = block.inner(popup);
@@ -124,7 +121,7 @@ class AiLogPopup {
     void renderFooter(List<Span> spans) {
         hint(spans, TuiIcons.HINT_SCROLL, "select");
         hint(spans, "PgUp/Dn", "detail");
-        hintLast(spans, "Esc", "back");
+        hintLast(spans, "Esc", "close");
     }
 
     private void renderMaster(Frame frame, Rect area) {
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 904455a6b085..482663d0a05f 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
@@ -1252,6 +1252,10 @@ class AiPanel {
     }
 
     void renderFooter(List<Span> spans) {
+        if (providerSwitchPopup.isVisible()) {
+            providerSwitchPopup.renderFooter(spans);
+            return;
+        }
         TuiHelper.hint(spans, "F8", "close");
         if (statsView) {
             TuiHelper.hint(spans, "Ctrl+U", "chat");
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AiProviderSwitchPopup.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AiProviderSwitchPopup.java
index 21656cd2d045..ac19042dedcd 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AiProviderSwitchPopup.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AiProviderSwitchPopup.java
@@ -133,9 +133,7 @@ final class AiProviderSwitchPopup {
     void render(Frame frame, Rect area) {
         int popupW = Math.max(1, Math.min(72, area.width() - 4));
         int popupH = Math.max(1, Math.min(choices.size() + 2, area.height() - 
4));
-        int x = area.left() + Math.max(0, (area.width() - popupW) / 2);
-        int y = area.top() + Math.max(0, (area.height() - popupH) / 4);
-        Rect popup = new Rect(x, y, Math.min(popupW, area.width()), 
Math.min(popupH, area.height()));
+        Rect popup = DialogHelper.centered(area, popupW, popupH);
         this.popupRect = popup;
         frame.renderWidget(Clear.INSTANCE, popup);
         Block block = Block.builder()
@@ -156,4 +154,9 @@ final class AiProviderSwitchPopup {
                 .build();
         frame.renderStatefulWidget(list, block.inner(popup), listState);
     }
+
+    void renderFooter(List<Span> spans) {
+        TuiHelper.hint(spans, "Enter", "select");
+        TuiHelper.hintLast(spans, "Esc", "close");
+    }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/BeansTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/BeansTab.java
index 200eedef6b7e..552f7dba8177 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/BeansTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/BeansTab.java
@@ -346,7 +346,6 @@ class BeansTab extends AbstractTableTab {
             hint(spans, "/", "filter");
         }
         hint(spans, "Tab", detailFocused ? "table" : "detail");
-        hint(spans, TuiIcons.HINT_SCROLL, "navigate");
         hintLast(spans, "PgUp/Dn", "scroll");
     }
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/BrowseTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/BrowseTab.java
index 0e39410bbf67..f1f7fa2a2319 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/BrowseTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/BrowseTab.java
@@ -507,7 +507,6 @@ class BrowseTab extends AbstractTab {
         hint(spans, "Esc", "back");
         if (view == VIEW_DETAIL) {
             hint(spans, "Tab", detailFocused ? "messages" : "detail");
-            hint(spans, TuiIcons.HINT_SCROLL, "navigate");
             hintLast(spans, "p", "pretty" + (prettyPrint ? " [on]" : ""));
         } else if (view == VIEW_MESSAGES) {
             hint(spans, "r", "refresh");
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 8bbd59f202da..b39116a00c6e 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
@@ -74,6 +74,7 @@ import sun.misc.Signal;
 
 import static org.apache.camel.dsl.jbang.core.commands.tui.TabRegistry.*;
 import static org.apache.camel.dsl.jbang.core.commands.tui.TuiHelper.hint;
+import static org.apache.camel.dsl.jbang.core.commands.tui.TuiHelper.hintLast;
 
 @Command(name = "monitor",
          description = "Live dashboard for monitoring Camel integrations",
@@ -393,6 +394,16 @@ public class CamelMonitor extends CamelCommand {
                 CamelMonitor.this.restartSelectedProcess();
             }
 
+            @Override
+            public void showKillConfirm() {
+                popupManager.showKillConfirm();
+            }
+
+            @Override
+            public void showConfirm(String title, String message, Runnable 
onConfirm) {
+                popupManager.showConfirm(title, message, onConfirm);
+            }
+
             @Override
             public void onRunPhantom(IntegrationInfo phantom) {
                 actionsPopup.openRunOptionsForPhantom(phantom);
@@ -2263,16 +2274,23 @@ public class CamelMonitor extends CamelCommand {
             return;
         }
 
+        // Modal popups own the footer no matter which tab is active, so the 
hints always describe
+        // the keys that the topmost dialog will actually receive.
         if (filesBrowser.isVisible()) {
             filesBrowser.renderFooter(spans);
+        } else if (popupManager.isKillConfirmVisible() || 
popupManager.isConfirmVisible()) {
+            hint(spans, "Enter", "confirm");
+            hintLast(spans, "Esc", "cancel");
         } else if (popupManager.isSwitchPopupVisible()) {
-            hint(spans, "Up/Down", "select");
             hint(spans, "Enter", "switch");
-            hint(spans, "Esc", "close");
+            hintLast(spans, "Esc", "close");
         } else if (popupManager.isMorePopupVisible()) {
-            hint(spans, "Up/Down", "select");
             hint(spans, "Enter", "open");
-            hint(spans, "Esc", "close");
+            hintLast(spans, "Esc", "close");
+        } else if (actionsPopup.isVisible()) {
+            actionsPopup.renderFooter(spans);
+        } else if (processControlPopup.isVisible()) {
+            processControlPopup.renderFooter(spans);
         } else if (shellPanel.isOpen()) {
             shellPanel.renderFooter(spans);
         } else if (aiPanel.isOpen()) {
@@ -2401,14 +2419,6 @@ public class CamelMonitor extends CamelCommand {
     }
 
     private int renderOverviewFooter(List<Span> spans) {
-        if (actionsPopup.isVisible()) {
-            actionsPopup.renderFooter(spans);
-            return 0;
-        }
-        if (processControlPopup.isVisible()) {
-            processControlPopup.renderFooter(spans);
-            return 0;
-        }
         tabRegistry.overviewTab().renderFooter(spans);
         int fKeyTotal = insertFKeyHints(spans);
         return fKeyTotal;
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CatalogTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CatalogTab.java
index 9b412effeec8..e217e2307a8f 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CatalogTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CatalogTab.java
@@ -426,7 +426,6 @@ class CatalogTab extends AbstractTableTab {
         } else {
             hint(spans, "/", "filter");
         }
-        hintLast(spans, TuiIcons.HINT_SCROLL, "navigate");
     }
 
     private int sortEntry(CatalogEntry a, CatalogEntry b) {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTab.java
index 57b95420b547..32bedc3a0a27 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTab.java
@@ -164,7 +164,6 @@ class CircuitBreakerTab extends AbstractTableTab {
     @Override
     public void renderFooter(List<Span> spans) {
         hint(spans, "Esc", "back");
-        hint(spans, TuiIcons.HINT_SCROLL, "navigate");
         hint(spans, "s", "sort");
     }
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ClasspathTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ClasspathTab.java
index 43bee09c26db..66635b2c3323 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ClasspathTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ClasspathTab.java
@@ -288,7 +288,6 @@ class ClasspathTab extends AbstractTab {
         } else {
             hint(spans, "/", "filter");
         }
-        hintLast(spans, TuiIcons.HINT_SCROLL, "navigate");
     }
 
     private void loadClasspath() {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConfigurationTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConfigurationTab.java
index 31494d01f090..03f696a635a5 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConfigurationTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConfigurationTab.java
@@ -453,7 +453,6 @@ class ConfigurationTab extends AbstractTableTab {
     public void renderFooter(List<Span> spans) {
         super.renderFooter(spans);
         hint(spans, "Tab", detailFocused ? "table" : "detail");
-        hint(spans, TuiIcons.HINT_SCROLL, "navigate");
         hintLast(spans, "PgUp/Dn", "scroll");
     }
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CveAuditTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CveAuditTab.java
index 8344a48bf128..f8771b1cbcbf 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CveAuditTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CveAuditTab.java
@@ -373,7 +373,6 @@ class CveAuditTab extends AbstractTableTab {
         hint(spans, "Esc", "back");
         hint(spans, "r", "rescan");
         super.renderFooter(spans);
-        hintLast(spans, "↑↓", "navigate");
     }
 
     private void rescan() {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DialogHelper.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DialogHelper.java
new file mode 100644
index 000000000000..afd8acd1d076
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DialogHelper.java
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.jbang.core.commands.tui;
+
+import dev.tamboui.layout.Rect;
+import dev.tamboui.style.Style;
+import dev.tamboui.terminal.Frame;
+import dev.tamboui.text.Line;
+import dev.tamboui.text.Span;
+import dev.tamboui.text.Text;
+import dev.tamboui.widgets.Clear;
+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.TextInputState;
+import dev.tamboui.widgets.paragraph.Paragraph;
+
+/**
+ * Shared building blocks for the small modal dialogs of the TUI so they all 
look and behave the same.
+ * <p>
+ * Conventions:
+ * <ul>
+ * <li>Small dialogs (confirm, single text input) are centered horizontally 
and sit in the upper third of the screen
+ * ({@link #centered(Rect, int, int)}). Tall list browsers and menus stay 
anchored just below the tab bar instead.</li>
+ * <li>Confirm dialogs use the warning color, or the error color for 
irreversible actions such as kill or delete. The
+ * title, border and message all share that one accent color.</li>
+ * <li>Key hints inside a dialog use the same badge style as the footer bar 
({@link TuiHelper#hintLine}).</li>
+ * <li>Every dialog clears the area beneath it before drawing.</li>
+ * </ul>
+ */
+final class DialogHelper {
+
+    /** Height of a confirm dialog: border, blank, message, blank, hints, 
border. */
+    static final int CONFIRM_HEIGHT = 6;
+
+    /** Height of a single-field input dialog: border, blank, field, blank, 
border. */
+    static final int INPUT_HEIGHT = 5;
+
+    private static final int CONFIRM_MIN_WIDTH = 34;
+    private static final int INPUT_MIN_WIDTH = 50;
+    private static final int INPUT_MAX_WIDTH = 64;
+
+    private DialogHelper() {
+    }
+
+    /**
+     * Computes the rectangle for a dialog of the given preferred size: 
centered horizontally and placed in the upper
+     * third of {@code area} vertically, clamped so it always fits inside the 
area.
+     */
+    static Rect centered(Rect area, int popupW, int popupH) {
+        int w = Math.max(1, Math.min(popupW, area.width()));
+        int h = Math.max(1, Math.min(popupH, area.height()));
+        int x = area.left() + Math.max(0, (area.width() - w) / 2);
+        int y = area.top() + Math.max(0, (area.height() - h) / 3);
+        return new Rect(x, y, w, h);
+    }
+
+    /**
+     * Clamps a dialog width to {@code [min, max]} while keeping a two-cell 
margin to each side of {@code area}. When
+     * the area is narrower than {@code min} the dialog shrinks to fit rather 
than overflowing.
+     */
+    static int clampWidth(Rect area, int min, int max) {
+        int w = Math.min(max, area.width() - 4);
+        w = Math.max(min, w);
+        return Math.max(1, Math.min(w, area.width() - 2));
+    }
+
+    /**
+     * Renders a confirm dialog that is accepted with Enter and cancelled with 
Esc.
+     *
+     * @param  title   dialog title without surrounding spaces, e.g. {@code 
"Confirm Quit"}
+     * @param  message the question, e.g. {@code "Quit the TUI?"}
+     * @param  danger  {@code true} for irreversible actions (kill, delete) to 
use the error color instead of warning
+     * @return         the rectangle the dialog was drawn in, for mouse 
hit-testing
+     */
+    static Rect renderConfirm(Frame frame, Rect area, String title, String 
message, boolean danger) {
+        return renderConfirm(frame, area, title, message, danger, "Enter", 
"confirm");
+    }
+
+    /**
+     * Renders a confirm dialog with a custom accept key, for dialogs that 
deliberately do not accept Enter.
+     *
+     * @see #renderConfirm(Frame, Rect, String, String, boolean)
+     */
+    static Rect renderConfirm(
+            Frame frame, Rect area, String title, String message, boolean 
danger,
+            String acceptKey, String acceptLabel) {
+        Style accent = danger ? Theme.error() : Theme.warning();
+        String titleText = " " + title + " ";
+        String msg = message.trim();
+        int popupW = clampWidth(area, CONFIRM_MIN_WIDTH, Math.max(msg.length() 
+ 6, titleText.length() + 4));
+        Rect popup = centered(area, popupW, CONFIRM_HEIGHT);
+
+        frame.renderWidget(Clear.INSTANCE, popup);
+        Block block = Block.builder()
+                .borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                .borderStyle(accent)
+                .title(Title.from(Line.from(Span.styled(titleText, 
accent.bold()))))
+                .build();
+        frame.renderWidget(block, popup);
+        Rect inner = block.inner(popup);
+        frame.renderWidget(
+                Paragraph.builder()
+                        .centered()
+                        .text(Text.from(
+                                Line.empty(),
+                                Line.from(Span.styled(msg, accent.bold())),
+                                Line.empty(),
+                                TuiHelper.hintLine(acceptKey, acceptLabel, 
"Esc", "cancel")))
+                        .build(),
+                inner);
+        return popup;
+    }
+
+    /**
+     * Renders a dialog with a single text field, e.g. for entering a new file 
name.
+     *
+     * @param  title       dialog title without surrounding spaces
+     * @param  state       the text input state; the caret is painted so the 
user sees where they type
+     * @param  placeholder dimmed text shown while the field is empty, may be 
{@code null}
+     * @return             the rectangle the dialog was drawn in, for mouse 
hit-testing
+     */
+    static Rect renderInputDialog(Frame frame, Rect area, String title, 
TextInputState state, String placeholder) {
+        int popupW = clampWidth(area, INPUT_MIN_WIDTH, INPUT_MAX_WIDTH);
+        Rect popup = centered(area, popupW, INPUT_HEIGHT);
+
+        frame.renderWidget(Clear.INSTANCE, popup);
+        Block block = Block.builder()
+                .borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                .title(" " + title + " ")
+                .build();
+        frame.renderWidget(block, popup);
+        Rect inner = block.inner(popup);
+
+        // Place the field on the middle row with a small horizontal margin, 
leaving a blank line above and below so
+        // the dialog does not feel cramped.
+        int pad = 2;
+        int fieldW = Math.max(1, inner.width() - 2 * pad);
+        int fieldY = inner.top() + Math.max(0, (inner.height() - 1) / 2);
+        FormHelper.renderTextField(frame, new Rect(inner.left() + pad, fieldY, 
fieldW, 1), state, true, placeholder);
+        return popup;
+    }
+}
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 ee753c3ac59d..ce1f0c11de77 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
@@ -47,6 +47,7 @@ import dev.tamboui.widgets.list.ScrollMode;
 import dev.tamboui.widgets.paragraph.Paragraph;
 import dev.tamboui.widgets.scrollbar.Scrollbar;
 import dev.tamboui.widgets.scrollbar.ScrollbarState;
+import org.apache.camel.dsl.jbang.core.common.OllamaDoctorSupport;
 
 import static org.apache.camel.dsl.jbang.core.commands.tui.TuiHelper.hint;
 import static org.apache.camel.dsl.jbang.core.commands.tui.TuiHelper.hintLast;
@@ -280,17 +281,15 @@ class DocViewerPopup {
     void renderFooter(List<Span> spans) {
         if (showViewer) {
             hint(spans, "Esc", "back");
-            hint(spans, "↑↓", "scroll");
             if (docContent != null) {
                 hint(spans, "t", "toc");
             }
             if (catalogEntryName != null) {
-                hintLast(spans, "o", "options");
+                hint(spans, "o", "options");
             }
         } else if (showPicker) {
-            hint(spans, "↑↓", "navigate");
             hint(spans, "Enter", "view");
-            hintLast(spans, "Esc", "back");
+            hintLast(spans, "Esc", "close");
         }
     }
 
@@ -381,31 +380,80 @@ class DocViewerPopup {
     }
 
     void openSetupAI() {
+        // current detection, mirroring the Doctor popup so both screens agree
+        String cloud = DoctorPopup.resolveCloudAiProvider();
+        OllamaDoctorSupport.Status ollama = OllamaDoctorSupport.detect();
+        String status;
+        if (cloud != null) {
+            status = "**Detected:** " + cloud + " (from environment)";
+        } else if (ollama.running() && ollama.models() != null && 
!ollama.models().isEmpty()) {
+            status = "**Detected:** Ollama at " + 
OllamaDoctorSupport.formatDisplayHost(ollama.baseUrl())
+                     + " with " + 
OllamaDoctorSupport.modelCountLabel(ollama.models());
+        } else if (ollama.running()) {
+            status = "**Detected:** Ollama is running but has no models. Run 
`ollama pull qwen2.5:32b`.";
+        } else {
+            status = "**Status:** No AI provider detected. Set an API key or 
start Ollama, then press F8.";
+        }
         String url = "http://localhost:"; + mcpPort + "/mcp";
-        String client = mcpConnectedClient != null ? mcpConnectedClient.get() 
: null;
-        String status = client != null
-                ? "**Connected:** " + client + "\n\nYour AI agent is already 
connected and ready to use."
-                : "**Status:** Waiting for connection";
-        openMarkdown("Setup MCP",
-                "# Setup MCP\n\n"
-                                  + status + "\n\n"
-                                  + "## Connect Claude Code\n\n"
-                                  + "Run this command in your terminal:\n\n"
-                                  + "    claude mcp add --transport http 
camel-tui " + url + "\n\n"
-                                  + "Then start a new Claude Code session. The 
TUI footer will turn green\n"
-                                  + "when the AI agent connects.\n\n"
-                                  + "## Alternative: .mcp.json\n\n"
-                                  + "A `.mcp.json` file is auto-generated in 
the current directory while the\n"
-                                  + "TUI runs with `--mcp`. AI agents that 
scan for `.mcp.json` will discover\n"
-                                  + "the MCP server automatically.\n\n"
-                                  + "## What the AI Can Do\n\n"
-                                  + "Once connected, your AI agent can:\n\n"
-                                  + "- See the TUI screen and follow your key 
presses\n"
-                                  + "- Navigate tabs and select integrations\n"
-                                  + "- Read route diagrams and health status\n"
-                                  + "- Send test messages to endpoints\n"
-                                  + "- Record VHS tapes for documentation\n\n"
-                                  + "Try asking: *\"What's on my Camel TUI 
screen right now?\"*\n");
+        openMarkdown("Setup AI",
+                "# Setup AI\n\n"
+                                 + status + "\n\n"
+                                 + "Press **F8** to open the AI prompt panel. 
The panel answers questions about your running\n"
+                                 + "integrations by calling built-in tools 
(routes, logs, errors, traces, health).\n\n"
+                                 + "## 1. Choose a provider\n\n"
+                                 + "The panel auto-detects a provider from 
environment variables, in this order:\n\n"
+                                 + "| Environment | Provider |\n"
+                                 + "|-------------|----------|\n"
+                                 + "| `ANTHROPIC_API_KEY` | Anthropic Claude 
|\n"
+                                 + "| `CLOUD_ML_REGION` + 
`ANTHROPIC_VERTEX_PROJECT_ID` | Vertex AI |\n"
+                                 + "| `AZURE_OPENAI_API_KEY` + 
`AZURE_OPENAI_ENDPOINT` | Azure OpenAI |\n"
+                                 + "| `GEMINI_API_KEY` | Google Gemini |\n"
+                                 + "| `OPENAI_API_KEY` | OpenAI |\n"
+                                 + "| `WATSONX_APIKEY` | IBM watsonx.ai |\n"
+                                 + "| `LLM_API_KEY` + `LLM_BASE_URL` | Any 
OpenAI-compatible server |\n"
+                                 + "| Ollama at `localhost:11434` | Local 
Ollama (no key needed) |\n\n"
+                                 + "Export the variable before starting the 
TUI, for example:\n\n"
+                                 + "    export ANTHROPIC_API_KEY=sk-ant-...\n"
+                                 + "    camel tui\n\n"
+                                 + "To pin a provider, model or URL regardless 
of the environment, open\n"
+                                 + "**F2 → Settings** and set *AI Provider*, 
*AI Model* and *AI Base URL*\n"
+                                 + "(stored as `camel.tui.ai.provider`, 
`camel.tui.ai.model`, `camel.tui.ai.url`).\n\n"
+                                 + "## 2. Local AI with Ollama (no API 
key)\n\n"
+                                 + "Install Ollama natively for GPU 
acceleration, pull a model, then press F8:\n\n"
+                                 + "    brew install ollama                    
      # macOS\n"
+                                 + "    curl -fsSL 
https://ollama.com/install.sh | sh  # Linux\n"
+                                 + "    ollama pull qwen2.5:32b\n\n"
+                                 + "The AI panel relies on tool calling. 
Models smaller than ~14B do not call tools\n"
+                                 + "reliably and answer from training data 
instead. Use at least 14B; 32B is recommended.\n\n"
+                                 + "| Model | RAM | Notes |\n"
+                                 + "|-------|-----|-------|\n"
+                                 + "| `qwen2.5:14b` | ~9 GB | Minimum 
recommended |\n"
+                                 + "| `qwen2.5:32b` | ~20 GB | Best balance of 
speed and quality |\n"
+                                 + "| `deepseek-r1:32b` | ~20 GB | Strong 
reasoning |\n"
+                                 + "| `hermes3:70b` | ~43 GB | Excellent tool 
calling, needs 64 GB+ |\n"
+                                 + "| `llama3.3:70b` | ~43 GB | Best open 
model, needs 64 GB+ |\n\n"
+                                 + "`camel infra run ollama` runs Ollama in 
Docker without GPU acceleration, which is\n"
+                                 + "much slower. Prefer the native install for 
development.\n\n"
+                                 + "## 3. OpenAI-compatible local servers\n\n"
+                                 + "LM Studio, vLLM, llama.cpp, GPT4All and 
similar servers work through `LLM_BASE_URL`:\n\n"
+                                 + "    export LLM_API_KEY=any-value\n"
+                                 + "    export 
LLM_BASE_URL=http://localhost:1234\n";
+                                 + "    camel tui\n\n"
+                                 + "`OPENAI_BASE_URL` is accepted as an 
alternative to `LLM_BASE_URL`.\n\n"
+                                 + "## 4. Using the AI panel\n\n"
+                                 + "- **F8** opens and closes the panel; 
**Enter** sends the prompt\n"
+                                 + "- **Ctrl+P** (or `/provider`) switches 
provider or model for the session\n"
+                                 + "- `/model <name>` switches the model, 
`/clear` resets the conversation\n"
+                                 + "- `/run`, `/infra` and `/send` run Camel 
CLI commands from the prompt\n"
+                                 + "- **Ctrl+U** toggles the usage view with 
token consumption\n"
+                                 + "- `/help` lists all slash commands; 
**↑/↓** recalls earlier prompts\n\n"
+                                 + "Check **F2 → Run Doctor** to verify the 
detected provider and Ollama models.\n\n"
+                                 + "## 5. Let an AI coding agent drive the TUI 
(optional)\n\n"
+                                 + "Start the TUI with `--mcp` to expose an 
MCP server, then connect an agent such as\n"
+                                 + "Claude Code:\n\n"
+                                 + "    claude mcp add --transport http 
camel-tui " + url + "\n\n"
+                                 + "The footer turns green when the agent 
connects. See **F2 → AI & MCP → MCP Info**\n"
+                                 + "for the available tools and a `.mcp.json` 
example.\n");
     }
 
     void openMcpInfo() {
@@ -488,24 +536,9 @@ class DocViewerPopup {
         } else {
             title = Title.from(" " + docTitle + " ");
         }
-        List<Span> footerSpans = new ArrayList<>();
-        footerSpans.add(Span.styled(" Esc ", Theme.hintKey()));
-        footerSpans.add(Span.raw(" back  "));
-        footerSpans.add(Span.styled(" ↑↓ ", Theme.hintKey()));
-        footerSpans.add(Span.raw(" scroll  "));
-        if (docContent != null) {
-            footerSpans.add(Span.styled(" t ", Theme.hintKey()));
-            footerSpans.add(Span.raw(" toc  "));
-        }
-        if (catalogEntryName != null) {
-            footerSpans.add(Span.styled(" o ", Theme.hintKey()));
-            footerSpans.add(Span.raw(" options "));
-        }
-        Title footer = Title.from(Line.from(footerSpans));
         Block block = Block.builder()
                 .borderType(BorderType.ROUNDED).borders(Borders.ALL)
                 .title(title)
-                .titleBottom(footer)
                 .build();
         if (docLines != null) {
             frame.renderWidget(block, area);
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DoctorPopup.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DoctorPopup.java
index f5f00b2da491..048a90839c1a 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DoctorPopup.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DoctorPopup.java
@@ -54,6 +54,43 @@ class DoctorPopup {
     private int mcpPort;
     private Supplier<String> mcpConnectedClient;
 
+    /** Fixed popup width; detail rows are word-wrapped to fit instead of 
widening the popup. */
+    private static final int POPUP_WIDTH = 64;
+    /** Detail rows are indented to line up with the value column. */
+    private static final String DETAIL_INDENT = "                    ";
+    private static final int DETAIL_WIDTH = POPUP_WIDTH - 2 - 
DETAIL_INDENT.length() - 1;
+
+    /**
+     * Adds a dimmed detail row under a check, word-wrapped so it never runs 
into the popup border.
+     */
+    private static void addDetail(List<Line> result, String text) {
+        for (String part : wrapWords(text, DETAIL_WIDTH)) {
+            result.add(Line.from(Span.styled(DETAIL_INDENT + part, 
Style.EMPTY.dim())));
+        }
+    }
+
+    static List<String> wrapWords(String text, int width) {
+        List<String> out = new ArrayList<>();
+        if (text == null || text.isBlank()) {
+            return out;
+        }
+        StringBuilder line = new StringBuilder();
+        for (String word : text.trim().split("\\s+")) {
+            if (line.length() > 0 && line.length() + 1 + word.length() > 
width) {
+                out.add(line.toString());
+                line.setLength(0);
+            }
+            if (line.length() > 0) {
+                line.append(' ');
+            }
+            line.append(word);
+        }
+        if (line.length() > 0) {
+            out.add(line.toString());
+        }
+        return out;
+    }
+
     boolean isVisible() {
         return visible;
     }
@@ -98,7 +135,7 @@ class DoctorPopup {
         if (lines == null || lines.isEmpty()) {
             return;
         }
-        int popupW = Math.min(62, area.width() - 4);
+        int popupW = Math.min(POPUP_WIDTH, area.width() - 4);
         int popupH = Math.min(lines.size() + 2, area.height() - 4);
         int x = area.left() + Math.max(0, (area.width() - popupW) / 2);
         int y = area.top() + 2;
@@ -116,7 +153,7 @@ class DoctorPopup {
     }
 
     void renderFooter(List<Span> spans) {
-        hintLast(spans, "Esc", "back");
+        hintLast(spans, "Esc", "close");
     }
 
     // ---- Checks ----
@@ -143,7 +180,7 @@ class DoctorPopup {
                 Span.raw(String.format("%-30s", version + " (" + vendor + 
")")),
                 Span.raw(" " + emoji)));
         if (status != null) {
-            result.add(Line.from(Span.styled("                    " + status, 
Style.EMPTY.dim())));
+            addDetail(result, status);
         }
     }
 
@@ -201,16 +238,14 @@ class DoctorPopup {
                     Span.styled(String.format("%-14s", "Maven"), 
Theme.muted()),
                     Span.raw(String.format("%-30s", "Resolution failed")),
                     Span.raw(" " + TuiIcons.FAIL)));
-            result.add(Line.from(Span.styled("                    " + 
TuiHelper.truncate(e.getMessage(), 40),
-                    Style.EMPTY.dim())));
+            addDetail(result, e.getMessage());
         } catch (Exception e) {
             result.add(Line.from(
                     Span.raw(TuiIcons.indent(TuiIcons.INFRA)),
                     Span.styled(String.format("%-14s", "Maven"), 
Theme.muted()),
                     Span.raw(String.format("%-30s", "Error")),
                     Span.raw(" " + TuiIcons.FAIL)));
-            result.add(Line.from(Span.styled("                    " + 
TuiHelper.truncate(e.getMessage(), 40),
-                    Style.EMPTY.dim())));
+            addDetail(result, e.getMessage());
         }
     }
 
@@ -313,10 +348,8 @@ class DoctorPopup {
                     Span.styled(String.format("%-14s", "AI"), Theme.muted()),
                     Span.raw(String.format("%-30s", "No API key configured")),
                     Span.raw(" " + TuiIcons.WARN)));
-            result.add(Line.from(Span.styled(
-                    "                    Set ANTHROPIC_API_KEY, 
AZURE_OPENAI_*, GEMINI_API_KEY, OPENAI_API_KEY,"
-                                             + " WATSONX_APIKEY, or start 
Ollama",
-                    Style.EMPTY.dim())));
+            addDetail(result, "Set ANTHROPIC_API_KEY, AZURE_OPENAI_*, 
GEMINI_API_KEY, OPENAI_API_KEY,"
+                              + " WATSONX_APIKEY, or start Ollama");
         }
     }
 
@@ -373,9 +406,7 @@ class DoctorPopup {
                     Span.styled(String.format("%-14s", "Ollama"), 
Theme.muted()),
                     Span.raw(String.format("%-30s", "Not detected 
(optional)")),
                     Span.raw(" " + TuiIcons.WARN)));
-            result.add(Line.from(Span.styled(
-                    "                    " + TuiHelper.truncate("Start Ollama 
(ollama serve) for local AI", 40),
-                    Style.EMPTY.dim())));
+            addDetail(result, "Start Ollama (ollama serve) for local AI");
         }
     }
 
@@ -394,8 +425,7 @@ class DoctorPopup {
                         Span.styled(String.format("%-14s", "MCP"), 
Theme.muted()),
                         Span.raw(String.format("%-30s", "Listening on port " + 
mcpPort)),
                         Span.raw(" " + TuiIcons.WARN)));
-                result.add(Line.from(Span.styled("                    No AI 
client connected",
-                        Style.EMPTY.dim())));
+                addDetail(result, "No AI client connected");
             }
         } else {
             result.add(Line.from(
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTab.java
index 305cf77220da..5801e48c71ba 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTab.java
@@ -392,7 +392,6 @@ class EndpointsTab extends AbstractTableTab {
     @Override
     public void renderFooter(List<Span> spans) {
         hint(spans, "Esc", "back");
-        hint(spans, TuiIcons.HINT_SCROLL, "navigate");
         hint(spans, "s", "sort");
         String[] filterLabels = { "all", "remote", "remote+stub" };
         hint(spans, "f", "filter [" + filterLabels[filter] + "]");
@@ -405,7 +404,6 @@ class EndpointsTab extends AbstractTableTab {
         hint(spans, "d", "detail " + (panelMode == PANEL_DETAIL ? "[on]" : 
"[off]"));
         if (panelMode == PANEL_DETAIL) {
             hint(spans, "Tab", detailFocused ? "table" : "detail");
-            hintLast(spans, TuiIcons.HINT_SCROLL, "navigate");
         }
     }
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java
index 32df7513bfd1..d46270942355 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java
@@ -430,7 +430,6 @@ class ErrorsTab extends AbstractTableTab {
             return;
         }
         hint(spans, "Esc", "back");
-        hint(spans, TuiIcons.HINT_SCROLL, "navigate");
         hint(spans, "PgUp/Dn", "detail");
         if (!wordWrap) {
             hint(spans, TuiIcons.HINT_H, "h-scroll");
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ExampleBrowserPopup.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ExampleBrowserPopup.java
index 54b8addc6f4d..e0fc29e29368 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ExampleBrowserPopup.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ExampleBrowserPopup.java
@@ -224,11 +224,11 @@ class ExampleBrowserPopup {
     }
 
     void renderFooter(List<Span> spans) {
-        TuiHelper.hint(spans, "↑↓", "navigate");
         TuiHelper.hint(spans, "r", "run");
-        TuiHelper.hint(spans, "Enter", currentFolder != null ? "run..." : 
"open/run...");
+        TuiHelper.hint(spans, "Enter", currentFolder != null ? "run..." : 
"open");
         TuiHelper.hint(spans, "d", "docs");
-        TuiHelper.hintLast(spans, "Esc", "back");
+        // inside a category Esc returns to the top level, at the top level it 
closes the browser
+        TuiHelper.hintLast(spans, "Esc", currentFolder != null ? "back" : 
"close");
     }
 
     SelectionContext getSelectionContext() {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FileActionsPopup.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FileActionsPopup.java
index 3a62de0ab9ad..b8c58096c0ee 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FileActionsPopup.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FileActionsPopup.java
@@ -21,11 +21,9 @@ import java.util.List;
 
 import dev.tamboui.layout.Padding;
 import dev.tamboui.layout.Rect;
-import dev.tamboui.style.Style;
 import dev.tamboui.terminal.Frame;
 import dev.tamboui.text.Line;
 import dev.tamboui.text.Span;
-import dev.tamboui.text.Text;
 import dev.tamboui.tui.event.KeyCode;
 import dev.tamboui.tui.event.KeyEvent;
 import dev.tamboui.widgets.Clear;
@@ -33,13 +31,11 @@ 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.TextInput;
 import dev.tamboui.widgets.input.TextInputState;
 import dev.tamboui.widgets.list.ListItem;
 import dev.tamboui.widgets.list.ListState;
 import dev.tamboui.widgets.list.ListWidget;
 import dev.tamboui.widgets.list.ScrollMode;
-import dev.tamboui.widgets.paragraph.Paragraph;
 
 /**
  * File-actions menu for the Source tab file list (opened with F12). Presents 
basic file management (new file, new
@@ -222,14 +218,16 @@ class FileActionsPopup {
     }
 
     private boolean handleConfirmKey(KeyEvent ke) {
-        // Delete is a destructive action: only an explicit "y" confirms it. 
Enter must NOT delete, so it
-        // (and Esc, "n", or any other key) simply returns to the menu.
+        // Delete is a destructive action: only an explicit "y" confirms it. 
Enter must NOT delete. Esc (or "n")
+        // returns to the menu; any other key is swallowed so a stray 
keystroke neither deletes nor dismisses.
         if (ke.code() == KeyCode.CHAR && "y".equalsIgnoreCase(ke.string())) {
             result = new Request(Action.DELETE, null);
             close();
             return true;
         }
-        phase = Phase.MENU;
+        if (ke.isCancel() || (ke.code() == KeyCode.CHAR && 
"n".equalsIgnoreCase(ke.string()))) {
+            phase = Phase.MENU;
+        }
         return true;
     }
 
@@ -285,69 +283,13 @@ class FileActionsPopup {
     }
 
     private void renderInput(Frame frame, Rect area) {
-        int popupW = Math.max(50, Math.min(64, area.width() - 4));
-        popupW = Math.min(popupW, area.width() - 2);
-        int popupH = 5;
-        int x = area.left() + Math.max(0, (area.width() - popupW) / 2);
-        int y = area.top() + Math.max(0, (area.height() - popupH) / 3);
-        Rect popup = new Rect(x, y, popupW, Math.min(popupH, area.height()));
-        this.popupRect = popup;
-
-        frame.renderWidget(Clear.INSTANCE, popup);
-        Block block = Block.builder()
-                .borderType(BorderType.ROUNDED).borders(Borders.ALL)
-                .borderStyle(Theme.borderFocused())
-                .title(Title.from(Line.from(Span.styled(" " + inputTitle + " 
", Theme.title().bold()))))
-                .build();
-        frame.renderWidget(block, popup);
-        Rect inner = block.inner(popup);
-
-        // Place the input on the middle row with a small horizontal margin, 
leaving a blank line above and below so
-        // the dialog does not feel cramped.
-        int pad = 2;
-        int fieldW = Math.max(1, inner.width() - 2 * pad);
-        int fieldY = inner.top() + Math.max(0, (inner.height() - 1) / 2);
-        Rect field = new Rect(inner.left() + pad, fieldY, fieldW, 1);
-
-        TextInput textInput = TextInput.builder()
-                .cursorStyle(Style.EMPTY.reversed())
-                .placeholder("name")
-                .build();
-        // Use renderWithCursor (not renderStatefulWidget, which calls 
render() and paints no cursor cell) so the
-        // caret is visible while typing the name.
-        textInput.renderWithCursor(field, frame.buffer(), inputState, frame);
+        this.popupRect = DialogHelper.renderInputDialog(frame, area, 
inputTitle, inputState, "name");
     }
 
     private void renderConfirm(Frame frame, Rect area) {
-        String msg = "Delete " + targetName + "?";
-        int popupW = Math.max(40, Math.min(60, msg.length() + 6));
-        popupW = Math.min(popupW, area.width() - 4);
-        int popupH = 6;
-        int x = area.left() + Math.max(0, (area.width() - popupW) / 2);
-        int y = area.top() + Math.max(0, (area.height() - popupH) / 3);
-        Rect popup = new Rect(x, y, Math.min(popupW, area.width()), 
Math.min(popupH, area.height()));
-        this.popupRect = popup;
-
-        frame.renderWidget(Clear.INSTANCE, popup);
-        Block block = Block.builder()
-                .borderType(BorderType.ROUNDED).borders(Borders.ALL)
-                .borderStyle(Theme.warning())
-                .title(Title.from(Line.from(Span.styled(" " + TuiIcons.DELETE 
+ " Delete file? ", Theme.warning().bold()))))
-                .build();
-        frame.renderWidget(block, popup);
-        Rect inner = block.inner(popup);
-        frame.renderWidget(
-                Paragraph.builder()
-                        .centered()
-                        .text(Text.from(
-                                Line.empty(),
-                                Line.from(Span.styled(msg, 
Theme.warning().bold())),
-                                Line.empty(),
-                                Line.from(
-                                        Span.styled("y", Style.EMPTY.bold()), 
Span.raw(" delete    "),
-                                        Span.styled("Esc", 
Style.EMPTY.bold()), Span.raw(" cancel"))))
-                        .build(),
-                inner);
+        // Deleting a file is irreversible, so this dialog is error-styled and 
deliberately accepts "y" only.
+        this.popupRect = DialogHelper.renderConfirm(frame, area, 
TuiIcons.DELETE + " Delete file?",
+                "Delete " + targetName + "?", true, "y", "delete");
     }
 
     void renderFooter(List<Span> spans) {
@@ -356,7 +298,6 @@ class FileActionsPopup {
         }
         switch (phase) {
             case MENU -> {
-                TuiHelper.hint(spans, TuiIcons.HINT_SCROLL, "navigate");
                 TuiHelper.hint(spans, "Enter", "select");
                 TuiHelper.hintLast(spans, "Esc", "close");
             }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FilesBrowser.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FilesBrowser.java
index 3a70c9a7042a..a22ebb8b92c4 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FilesBrowser.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FilesBrowser.java
@@ -365,9 +365,8 @@ class FilesBrowser {
         if (sourceViewer.isVisible()) {
             sourceViewer.renderFooter(spans);
         } else {
-            TuiHelper.hint(spans, TuiIcons.HINT_SCROLL, "navigate");
             TuiHelper.hint(spans, "Enter", "open");
-            TuiHelper.hint(spans, "Esc", "close");
+            TuiHelper.hintLast(spans, "Esc", "close");
         }
     }
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FolderBrowser.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FolderBrowser.java
index afa358b68989..8f6f76e08e6e 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FolderBrowser.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FolderBrowser.java
@@ -409,7 +409,6 @@ class FolderBrowser {
             sourceViewer.renderFooter(spans);
             return;
         }
-        TuiHelper.hint(spans, TuiIcons.HINT_SCROLL, "navigate");
         TuiHelper.hint(spans, "Enter", fileSelectMode ? "select" : "open");
         if (!fileSelectMode) {
             TuiHelper.hint(spans, "Tab", "select");
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FolderInputPopup.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FolderInputPopup.java
index a98f315fb090..58fad1cc8979 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FolderInputPopup.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FolderInputPopup.java
@@ -23,9 +23,7 @@ import java.util.List;
 import java.util.function.BiConsumer;
 
 import dev.tamboui.layout.Rect;
-import dev.tamboui.style.Style;
 import dev.tamboui.terminal.Frame;
-import dev.tamboui.text.Line;
 import dev.tamboui.text.Span;
 import dev.tamboui.tui.event.KeyCode;
 import dev.tamboui.tui.event.KeyEvent;
@@ -34,9 +32,7 @@ import dev.tamboui.widgets.Clear;
 import dev.tamboui.widgets.block.Block;
 import dev.tamboui.widgets.block.BorderType;
 import dev.tamboui.widgets.block.Borders;
-import dev.tamboui.widgets.input.TextInput;
 import dev.tamboui.widgets.input.TextInputState;
-import dev.tamboui.widgets.paragraph.Paragraph;
 import org.apache.camel.dsl.jbang.core.common.LauncherHelper;
 
 class FolderInputPopup {
@@ -201,11 +197,11 @@ class FolderInputPopup {
             folderBrowser.renderFooter(spans);
         } else if (showInput) {
             if (!folderHistory.isEmpty()) {
-                TuiHelper.hint(spans, "↑↓", "history");
+                TuiHelper.hint(spans, TuiIcons.HINT_SCROLL, "history");
             }
             TuiHelper.hint(spans, "Tab", "browse");
             TuiHelper.hint(spans, "Enter", "open");
-            TuiHelper.hintLast(spans, "Esc", "back");
+            TuiHelper.hintLast(spans, "Esc", "cancel");
         }
     }
 
@@ -332,10 +328,7 @@ class FolderInputPopup {
 
     private void renderInput(Frame frame, Rect area) {
         int popupW = Math.min(70, area.width() - 4);
-        int popupH = 4;
-        int x = area.left() + Math.max(0, (area.width() - popupW) / 2);
-        int y = area.top() + Math.max(0, (area.height() - 17) / 4);
-        Rect popup = new Rect(x, y, Math.min(popupW, area.width()), 
Math.min(popupH, area.height()));
+        Rect popup = DialogHelper.centered(area, popupW, 
DialogHelper.INPUT_HEIGHT);
 
         frame.renderWidget(Clear.INSTANCE, popup);
 
@@ -346,20 +339,15 @@ class FolderInputPopup {
         frame.renderWidget(block, popup);
         Rect inner = block.inner(popup);
 
+        int pad = 1;
         int labelW = 9;
-        int fieldW = inner.width() - labelW;
-        int row = inner.top();
-        int ix = inner.left();
-
-        Rect labelArea = new Rect(ix, row, labelW, 1);
-        frame.renderWidget(Paragraph.from(Line.from(Span.styled("Folder:", 
Style.EMPTY.bold()))), labelArea);
-        Rect inputArea = new Rect(ix + labelW, row, fieldW, 1);
-        TextInput textInput = TextInput.builder()
-                .cursorStyle(Style.EMPTY.reversed())
-                .placeholder("/path/to/folder")
-                .build();
-        // renderWithCursor (not renderStatefulWidget) so the caret is visible 
while typing
-        textInput.renderWithCursor(inputArea, frame.buffer(), inputState, 
frame);
+        int fieldW = Math.max(1, inner.width() - labelW - 2 * pad);
+        // middle row, with a blank line above and below like the other input 
dialogs
+        int row = inner.top() + Math.max(0, (inner.height() - 1) / 2);
+        int ix = inner.left() + pad;
+
+        FormHelper.renderLabel(frame, ix, row, labelW, "Folder:", true);
+        FormHelper.renderTextField(frame, new Rect(ix + labelW, row, fieldW, 
1), inputState, true, "/path/to/folder");
     }
 
     private void doLaunchFolder(String folder, String pomPath, String 
displayName, List<String> extraArgs) {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FormHelper.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FormHelper.java
index 5dcb71b56f3f..5e7579981bf3 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FormHelper.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FormHelper.java
@@ -23,6 +23,7 @@ import dev.tamboui.text.Line;
 import dev.tamboui.text.Span;
 import dev.tamboui.tui.event.KeyCode;
 import dev.tamboui.tui.event.KeyEvent;
+import dev.tamboui.widgets.input.TextInput;
 import dev.tamboui.widgets.input.TextInputState;
 import dev.tamboui.widgets.paragraph.Paragraph;
 
@@ -58,6 +59,34 @@ final class FormHelper {
         frame.renderWidget(Paragraph.from(Line.from(Span.styled(label, 
style))), labelArea);
     }
 
+    /**
+     * Renders a single-line text field. The active field paints a reversed 
caret at the cursor so the user can see
+     * where they type; an inactive field shows its text, or the placeholder 
dimmed while it is empty.
+     *
+     * @param state       the field state, may be {@code null} for an inactive 
field with no value yet
+     * @param active      whether the field currently has focus
+     * @param placeholder dimmed text shown while the field is empty, may be 
{@code null}
+     */
+    static void renderTextField(Frame frame, Rect area, TextInputState state, 
boolean active, String placeholder) {
+        if (active && state != null) {
+            TextInput.Builder builder = 
TextInput.builder().cursorStyle(Style.EMPTY.reversed());
+            if (placeholder != null) {
+                builder.placeholder(placeholder);
+            }
+            // renderWithCursor (not renderStatefulWidget, which paints no 
cursor cell) so the caret is visible
+            builder.build().renderWithCursor(area, frame.buffer(), state, 
frame);
+            return;
+        }
+        String text = state != null ? state.text() : "";
+        if (text.isEmpty()) {
+            if (placeholder != null) {
+                
frame.renderWidget(Paragraph.from(Line.from(Span.styled(placeholder, 
Style.EMPTY.dim()))), area);
+            }
+        } else {
+            frame.renderWidget(Paragraph.from(Line.from(Span.raw(text))), 
area);
+        }
+    }
+
     static void handlePaste(String text, TextInputState target) {
         if (text == null || text.isEmpty() || target == null) {
             return;
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HelpOverlay.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HelpOverlay.java
index 2e1dcafaf377..ca2aa1a6b4c6 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HelpOverlay.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HelpOverlay.java
@@ -21,7 +21,6 @@ import java.util.List;
 import dev.tamboui.layout.Rect;
 import dev.tamboui.markdown.MarkdownView;
 import dev.tamboui.terminal.Frame;
-import dev.tamboui.text.Line;
 import dev.tamboui.text.Span;
 import dev.tamboui.tui.event.KeyCode;
 import dev.tamboui.tui.event.KeyEvent;
@@ -29,9 +28,7 @@ import dev.tamboui.widgets.Clear;
 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 static org.apache.camel.dsl.jbang.core.commands.tui.TuiHelper.hint;
 import static org.apache.camel.dsl.jbang.core.commands.tui.TuiHelper.hintLast;
 
 class HelpOverlay {
@@ -91,12 +88,10 @@ class HelpOverlay {
         frame.renderWidget(Clear.INSTANCE, area);
         Rect popup = new Rect(area.left() + 2, area.top() + 1, area.width() - 
4, area.height() - 2);
 
+        // key hints are shown in the footer bar only, not repeated in the 
bottom border
         Block block = Block.builder()
                 .borderType(BorderType.ROUNDED).borders(Borders.ALL)
                 .title(" Help ")
-                .titleBottom(Title.from(Line.from(
-                        Span.styled(" F1/? ", Theme.hintKey()), Span.raw(" 
close "),
-                        Span.styled(" " + TuiIcons.HINT_SCROLL + " ", 
Theme.hintKey()), Span.raw(" scroll "))))
                 .build();
 
         MarkdownView view = MarkdownView.builder()
@@ -109,7 +104,6 @@ class HelpOverlay {
     }
 
     void renderFooter(List<Span> spans) {
-        hint(spans, TuiIcons.HINT_SCROLL, "scroll");
-        hintLast(spans, "Esc", "close");
+        hintLast(spans, "Esc/F1", "close");
     }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTab.java
index 41be33d787b7..4db498ef1f9d 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTab.java
@@ -802,7 +802,6 @@ class HistoryTab extends AbstractTab {
             if (!showWaterfall) {
                 hint(spans, "Tab", detailFocused ? "table" : "detail");
             }
-            hint(spans, TuiIcons.HINT_SCROLL, "navigate");
             if (!showWaterfall && !traceWordWrap) {
                 hint(spans, TuiIcons.HINT_H, "h-scroll");
             }
@@ -815,7 +814,6 @@ class HistoryTab extends AbstractTab {
             hintLast(spans, "w", "wrap" + (traceWordWrap ? " [on]" : " 
[off]"));
         } else if (tracerActive) {
             hint(spans, "Esc", "back");
-            hint(spans, TuiIcons.HINT_SCROLL, "navigate");
             hint(spans, "s", "sort");
             hint(spans, "n", "description" + (showDescription ? " [on]" : ""));
             hint(spans, "d", "diagram");
@@ -826,7 +824,6 @@ class HistoryTab extends AbstractTab {
             if (!showWaterfall) {
                 hint(spans, "Tab", detailFocused ? "table" : "detail");
             }
-            hint(spans, TuiIcons.HINT_SCROLL, "navigate");
             if (!showWaterfall && !historyWordWrap) {
                 hint(spans, TuiIcons.HINT_H, "h-scroll");
             }
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 4b60ca179152..b724005b38ef 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
@@ -234,7 +234,6 @@ class HttpTab extends AbstractTableTab {
         }
         if (showSpec) {
             hint(spans, "c/Esc", "close");
-            hint(spans, TuiIcons.HINT_SCROLL, "scroll");
             hintLast(spans, "PgUp/PgDn", "page");
             return;
         }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/InfraBrowserPopup.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/InfraBrowserPopup.java
index 1c0cbcda56ec..8d5d7b7b2355 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/InfraBrowserPopup.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/InfraBrowserPopup.java
@@ -42,7 +42,6 @@ import dev.tamboui.widgets.Clear;
 import dev.tamboui.widgets.block.Block;
 import dev.tamboui.widgets.block.BorderType;
 import dev.tamboui.widgets.block.Borders;
-import dev.tamboui.widgets.input.TextInput;
 import dev.tamboui.widgets.input.TextInputState;
 import dev.tamboui.widgets.list.ListItem;
 import dev.tamboui.widgets.list.ListState;
@@ -195,7 +194,6 @@ class InfraBrowserPopup {
         if (showPortDialog) {
             boolean hasMultiImpl = selectedService != null && 
selectedService.implementations().size() > 1;
             if (hasMultiImpl) {
-                TuiHelper.hint(spans, TuiIcons.HINT_SCROLL, "navigate");
                 if (portDialogRow == 0) {
                     TuiHelper.hint(spans, "Space", "cycle");
                 }
@@ -203,9 +201,8 @@ class InfraBrowserPopup {
             TuiHelper.hint(spans, "Enter", "run");
             TuiHelper.hintLast(spans, "Esc", "back");
         } else if (showBrowser) {
-            TuiHelper.hint(spans, "↑↓", "navigate");
             TuiHelper.hint(spans, "Enter", "select");
-            TuiHelper.hintLast(spans, "Esc", "back");
+            TuiHelper.hintLast(spans, "Esc", "close");
         }
     }
 
@@ -458,20 +455,7 @@ class InfraBrowserPopup {
         Rect portLabelArea = new Rect(ix, row, labelW, 1);
         frame.renderWidget(Paragraph.from(Line.from(Span.styled("Port:", 
portLabelStyle))), portLabelArea);
         Rect portArea = new Rect(ix + labelW, row, fieldW, 1);
-        if (portDialogRow == 1) {
-            TextInput textInput = TextInput.builder()
-                    .cursorStyle(Style.EMPTY.reversed())
-                    .placeholder("default")
-                    .build();
-            // renderWithCursor (not renderStatefulWidget) so the caret is 
painted on the active field
-            textInput.renderWithCursor(portArea, frame.buffer(), portState, 
frame);
-        } else {
-            String portText = portState != null ? portState.text() : "";
-            frame.renderWidget(Paragraph.from(Line.from(
-                    Span.styled(portText.isEmpty() ? "default" : portText,
-                            portText.isEmpty() ? Style.EMPTY.dim() : 
Style.EMPTY))),
-                    portArea);
-        }
+        FormHelper.renderTextField(frame, portArea, portState, portDialogRow 
== 1, "default");
     }
 
     private void handlePortInput(KeyEvent ke) {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java
index c09af0e382c3..98777f9319c8 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java
@@ -414,7 +414,6 @@ class LogTab extends AbstractTab {
         }
         if (showLogLevelPopup) {
             hint(spans, "Esc", "cancel");
-            hint(spans, TuiIcons.HINT_SCROLL, "navigate");
             hintLast(spans, "Enter", "set level");
             return;
         }
@@ -424,7 +423,6 @@ class LogTab extends AbstractTab {
         } else {
             hint(spans, "Esc", "back");
         }
-        hint(spans, TuiIcons.HINT_SCROLL, "scroll");
         search.renderSearchHints(spans);
         hint(spans, "w", "wrap" + (wordWrap ? " [on]" : " [off]"));
         if (!ctx.isInfraSelected()) {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MavenDependenciesTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MavenDependenciesTab.java
index 27feb4d7e24a..828b12368248 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MavenDependenciesTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MavenDependenciesTab.java
@@ -320,7 +320,6 @@ class MavenDependenciesTab extends AbstractTableTab {
         } else {
             hint(spans, "/", "filter");
         }
-        hintLast(spans, TuiIcons.HINT_SCROLL, "navigate");
     }
 
     private int sortDep(DependencyLoader.DepEntry a, DependencyLoader.DepEntry 
b) {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java
index fcc6af4bfd90..3fc3a2c2f016 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java
@@ -40,6 +40,7 @@ import org.apache.camel.util.json.JsonArray;
 import org.apache.camel.util.json.JsonObject;
 
 import static org.apache.camel.dsl.jbang.core.commands.tui.TuiHelper.hint;
+import static org.apache.camel.dsl.jbang.core.commands.tui.TuiHelper.hintLast;
 
 /**
  * Facade that exposes monitor state and actions to the MCP server.
@@ -612,13 +613,11 @@ class McpFacade {
             filesBrowser.renderFooter(spans);
         } else if (bridge.isSwitchPopupVisible() || 
bridge.isMorePopupVisible()) {
             if (bridge.isSwitchPopupVisible()) {
-                hint(spans, "Up/Down", "select");
                 hint(spans, "Enter", "switch");
-                hint(spans, "Esc", "close");
+                hintLast(spans, "Esc", "close");
             } else {
-                hint(spans, "Up/Down", "select");
                 hint(spans, "Enter", "open");
-                hint(spans, "Esc", "close");
+                hintLast(spans, "Esc", "close");
             }
         } else {
             MonitorTab tab = bridge.activeTab();
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpLogPopup.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpLogPopup.java
index cbfe67e6208a..79d5b984e4b4 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpLogPopup.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpLogPopup.java
@@ -128,7 +128,7 @@ class McpLogPopup {
     void renderFooter(List<Span> spans) {
         hint(spans, TuiIcons.HINT_SCROLL, "select");
         hint(spans, "PgUp/Dn", "detail");
-        hintLast(spans, "Esc", "back");
+        hintLast(spans, "Esc", "close");
     }
 
     private void renderMaster(Frame frame, Rect area) {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MetricsTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MetricsTab.java
index 4ef4cd0da358..0dd26bc5478f 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MetricsTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MetricsTab.java
@@ -616,7 +616,6 @@ class MetricsTab extends AbstractTableTab {
     public void renderFooter(List<Span> spans) {
         if (showRaw) {
             hint(spans, "Esc", "close");
-            hint(spans, TuiIcons.HINT_SCROLL, "scroll");
             hint(spans, "PgUp/Dn", "page");
             hint(spans, "F5", "refresh");
             return;
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/NetworkTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/NetworkTab.java
index 6b925a9b2e2a..7e23b8cdd4a7 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/NetworkTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/NetworkTab.java
@@ -404,7 +404,6 @@ class NetworkTab extends AbstractTableTab {
     @Override
     public void renderFooter(List<Span> spans) {
         hint(spans, "Esc", "back");
-        hint(spans, TuiIcons.HINT_SCROLL, "navigate");
         hint(spans, "s", "sort");
         String chartLabel = switch (chartMode) {
             case CHART_ALL -> "[all]";
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OptionsViewerPopup.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OptionsViewerPopup.java
index 779232cf7ec8..578140eaceec 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OptionsViewerPopup.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OptionsViewerPopup.java
@@ -382,7 +382,6 @@ class OptionsViewerPopup {
         Block block = Block.builder()
                 .borderType(BorderType.ROUNDED).borders(Borders.ALL)
                 .title(buildTitle())
-                .titleBottom(buildFooter())
                 .build();
         frame.renderWidget(block, area);
 
@@ -436,24 +435,8 @@ class OptionsViewerPopup {
         return Title.from(Line.from(spans));
     }
 
-    private Title buildFooter() {
-        List<Span> spans = new ArrayList<>();
-        spans.add(Span.styled(" Esc ", Theme.hintKey()));
-        spans.add(Span.raw(" back  "));
-        spans.add(Span.styled(" ↑↓ ", Theme.hintKey()));
-        spans.add(Span.raw(" scroll  "));
-        if (tabLabels != null && tabLabels.length > 1) {
-            spans.add(Span.styled(" ←→ ", Theme.hintKey()));
-            spans.add(Span.raw(" tab  "));
-        }
-        spans.add(Span.styled(" d ", Theme.hintKey()));
-        spans.add(Span.raw(" doc "));
-        return Title.from(Line.from(spans));
-    }
-
     void renderFooter(List<Span> spans) {
         hint(spans, "Esc", "back");
-        hint(spans, "↑↓", "scroll");
         if (tabLabels != null && tabLabels.length > 1) {
             hint(spans, "←→", "tab");
         }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java
index c659fc645d13..e1c7bda1c0fc 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java
@@ -1033,7 +1033,6 @@ class OverviewTab extends AbstractTab {
         if (ctx.selectedPid != null) {
             hint(spans, "Esc", "unselect");
         }
-        hint(spans, TuiIcons.HINT_SCROLL, "navigate");
         if (!ctx.infraData.get().isEmpty()) {
             hint(spans, "Tab", infraFocused ? "integrations" : "infra");
         }
@@ -1050,9 +1049,7 @@ class OverviewTab extends AbstractTab {
                 default -> "[off]";
             });
         }
-        if (ctx.selectedPid != null) {
-            hint(spans, "F10", "run");
-        }
+        // F10 is added by the global F-key hints, so it must not be repeated 
here.
     }
 
     @Override
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/PopupManager.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/PopupManager.java
index 2f7250a2c4ae..2184594eed6e 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/PopupManager.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/PopupManager.java
@@ -26,7 +26,6 @@ import dev.tamboui.terminal.Frame;
 import dev.tamboui.text.CharWidth;
 import dev.tamboui.text.Line;
 import dev.tamboui.text.Span;
-import dev.tamboui.text.Text;
 import dev.tamboui.tui.event.KeyCode;
 import dev.tamboui.tui.event.KeyEvent;
 import dev.tamboui.tui.event.MouseEvent;
@@ -39,7 +38,6 @@ import dev.tamboui.widgets.list.ListItem;
 import dev.tamboui.widgets.list.ListState;
 import dev.tamboui.widgets.list.ListWidget;
 import dev.tamboui.widgets.list.ScrollMode;
-import dev.tamboui.widgets.paragraph.Paragraph;
 import dev.tamboui.widgets.scrollbar.Scrollbar;
 import dev.tamboui.widgets.scrollbar.ScrollbarState;
 
@@ -410,11 +408,14 @@ class PopupManager {
         return true;
     }
 
+    // Confirm dialogs share one key contract: Enter accepts, Esc cancels, and 
every other key is
+    // swallowed so a stray keystroke can neither dismiss nor trigger a 
destructive action.
+
     private boolean handleKillConfirmKeys(KeyEvent ke) {
         if (ke.isConfirm()) {
             showKillConfirm = false;
             callbacks.stopSelectedProcess(true);
-        } else {
+        } else if (ke.isCancel()) {
             showKillConfirm = false;
         }
         return true;
@@ -428,7 +429,7 @@ class PopupManager {
             if (cb != null) {
                 cb.run();
             }
-        } else {
+        } else if (ke.isCancel()) {
             showConfirm = false;
             confirmCallback = null;
         }
@@ -695,69 +696,14 @@ class PopupManager {
     }
 
     void renderKillConfirm(Frame frame, Rect area) {
-        String name = ctx.selectedName();
-        String msg = " Kill " + name + " (PID: " + ctx.selectedPid + ")? ";
-        int popupW = Math.max(34, msg.length() + 4);
-        int popupH = 6;
-        int x = area.left() + Math.max(0, (area.width() - popupW) / 2);
-        int y = area.top() + Math.max(0, (area.height() - popupH) / 3);
-        Rect popup = new Rect(x, y, Math.min(popupW, area.width()), 
Math.min(popupH, area.height()));
-
-        frame.renderWidget(Clear.INSTANCE, popup);
-        Block block = Block.builder()
-                .borderType(BorderType.ROUNDED).borders(Borders.ALL)
-                .borderStyle(Theme.error())
-                .title(" Confirm Kill ")
-                .build();
-        frame.renderWidget(block, popup);
-        Rect inner = block.inner(popup);
-        frame.renderWidget(
-                Paragraph.builder()
-                        .centered()
-                        .text(Text.from(
-                                Line.empty(),
-                                Line.from(Span.styled(msg, 
Theme.error().bold())),
-                                Line.empty(),
-                                Line.from(
-                                        Span.styled("Enter", 
Style.EMPTY.bold()),
-                                        Span.raw(" confirm    "),
-                                        Span.styled("Esc", Style.EMPTY.bold()),
-                                        Span.raw(" cancel"))))
-                        .build(),
-                inner);
+        String msg = "Kill " + ctx.selectedName() + " (PID: " + 
ctx.selectedPid + ")?";
+        DialogHelper.renderConfirm(frame, area, "Confirm Kill", msg, true);
     }
 
     void renderConfirm(Frame frame, Rect area) {
         String msg = confirmMessage != null ? confirmMessage : "";
-        String title = confirmTitle != null ? " " + confirmTitle + " " : " 
Confirm ";
-        int popupW = Math.max(34, Math.max(msg.length() + 4, title.length() + 
4));
-        int popupH = 6;
-        int x = area.left() + Math.max(0, (area.width() - popupW) / 2);
-        int y = area.top() + Math.max(0, (area.height() - popupH) / 3);
-        Rect popup = new Rect(x, y, Math.min(popupW, area.width()), 
Math.min(popupH, area.height()));
-
-        frame.renderWidget(Clear.INSTANCE, popup);
-        Block block = Block.builder()
-                .borderType(BorderType.ROUNDED).borders(Borders.ALL)
-                .borderStyle(Theme.warning())
-                .title(title)
-                .build();
-        frame.renderWidget(block, popup);
-        Rect inner = block.inner(popup);
-        frame.renderWidget(
-                Paragraph.builder()
-                        .centered()
-                        .text(Text.from(
-                                Line.empty(),
-                                Line.from(Span.styled(msg, 
Theme.warning().bold())),
-                                Line.empty(),
-                                Line.from(
-                                        Span.styled("Enter", 
Style.EMPTY.bold()),
-                                        Span.raw(" confirm    "),
-                                        Span.styled("Esc", Style.EMPTY.bold()),
-                                        Span.raw(" cancel"))))
-                        .build(),
-                inner);
+        String title = confirmTitle != null ? confirmTitle : "Confirm";
+        DialogHelper.renderConfirm(frame, area, title, msg, false);
     }
 
     int[] morePopupShortcut(KeyEvent ke) {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ProcessControlPopup.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ProcessControlPopup.java
index 8e8891343908..fd7e156e71df 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ProcessControlPopup.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ProcessControlPopup.java
@@ -63,6 +63,16 @@ class ProcessControlPopup {
 
         void restartSelectedProcess();
 
+        /**
+         * Shows the kill confirm dialog; the dialog itself force-kills the 
selected process on Enter.
+         */
+        void showKillConfirm();
+
+        /**
+         * Shows a generic confirm dialog that runs {@code onConfirm} on Enter 
and does nothing on Esc.
+         */
+        void showConfirm(String title, String message, Runnable onConfirm);
+
         void onRunPhantom(IntegrationInfo phantom);
 
         void onStopAll();
@@ -181,7 +191,7 @@ class ProcessControlPopup {
 
     void renderFooter(List<Span> spans) {
         hint(spans, "Enter", "select");
-        hintLast(spans, "Esc", "cancel");
+        hintLast(spans, "Esc", "close");
     }
 
     private void executeAction(ControlAction action) {
@@ -204,10 +214,30 @@ class ProcessControlPopup {
             }
             case STOP_ROUTES -> actions.sendRouteCommand(ctx.selectedPid, "*", 
"stop");
             case START_ROUTES -> actions.sendRouteCommand(ctx.selectedPid, 
"*", "start");
-            case RESTART -> actions.restartSelectedProcess();
-            case STOP -> actions.stopSelectedProcess(false);
-            case KILL -> actions.stopSelectedProcess(true);
+            case RESTART -> confirmThen("Confirm Restart", "Restart", () -> 
actions.restartSelectedProcess());
+            case STOP -> confirmThen("Confirm Stop", "Stop", () -> 
actions.stopSelectedProcess(false));
+            case KILL -> {
+                // Kill has a dedicated (error-styled) confirm dialog that 
performs the force-kill itself.
+                if (ctx.confirmActions) {
+                    actions.showKillConfirm();
+                } else {
+                    actions.stopSelectedProcess(true);
+                }
+            }
             case STOP_ALL -> actions.onStopAll();
         }
     }
+
+    /**
+     * Runs {@code action} directly when confirmations are disabled in 
settings, otherwise asks first. Stop, Restart and
+     * Kill all terminate a running process, so they share the same "are you 
sure?" gate as Quit and Stop All.
+     */
+    private void confirmThen(String title, String verb, Runnable action) {
+        if (!ctx.confirmActions) {
+            action.run();
+            return;
+        }
+        String msg = " " + verb + " " + ctx.selectedName() + " (PID: " + 
ctx.selectedPid + ")? ";
+        actions.showConfirm(title, msg, action);
+    }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ProcessTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ProcessTab.java
index 5eb1cb033b47..6ca0e4ebb8c1 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ProcessTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ProcessTab.java
@@ -199,7 +199,6 @@ class ProcessTab extends AbstractTab {
     @Override
     public void renderFooter(List<Span> spans) {
         hint(spans, "Esc", "back");
-        hint(spans, TuiIcons.HINT_SCROLL, "scroll");
         hint(spans, "w", "wrap [" + (wrap ? "on" : "off") + "]");
     }
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RefactorPopup.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RefactorPopup.java
index d9355a2d1976..a24dc69cef76 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RefactorPopup.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RefactorPopup.java
@@ -21,7 +21,6 @@ import java.util.List;
 
 import dev.tamboui.layout.Padding;
 import dev.tamboui.layout.Rect;
-import dev.tamboui.style.Style;
 import dev.tamboui.terminal.Frame;
 import dev.tamboui.text.Line;
 import dev.tamboui.text.Span;
@@ -32,7 +31,6 @@ 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.TextInput;
 import dev.tamboui.widgets.input.TextInputState;
 import dev.tamboui.widgets.list.ListItem;
 import dev.tamboui.widgets.list.ListState;
@@ -244,32 +242,7 @@ class RefactorPopup {
     }
 
     private void renderInput(Frame frame, Rect area) {
-        int popupW = Math.max(50, Math.min(64, area.width() - 4));
-        popupW = Math.min(popupW, area.width() - 2);
-        int popupH = 5;
-        int x = area.left() + Math.max(0, (area.width() - popupW) / 2);
-        int y = area.top() + Math.max(0, (area.height() - popupH) / 3);
-        Rect popup = new Rect(x, y, popupW, Math.min(popupH, area.height()));
-
-        frame.renderWidget(Clear.INSTANCE, popup);
-        Block block = Block.builder()
-                .borderType(BorderType.ROUNDED).borders(Borders.ALL)
-                .borderStyle(Theme.borderFocused())
-                .title(Title.from(Line.from(Span.styled(" " + inputTitle + " 
", Theme.title().bold()))))
-                .build();
-        frame.renderWidget(block, popup);
-        Rect inner = block.inner(popup);
-
-        int pad = 2;
-        int fieldW = Math.max(1, inner.width() - 2 * pad);
-        int fieldY = inner.top() + Math.max(0, (inner.height() - 1) / 2);
-        Rect field = new Rect(inner.left() + pad, fieldY, fieldW, 1);
-
-        TextInput textInput = TextInput.builder()
-                .cursorStyle(Style.EMPTY.reversed())
-                .placeholder(inputPlaceholder)
-                .build();
-        textInput.renderWithCursor(field, frame.buffer(), inputState, frame);
+        DialogHelper.renderInputDialog(frame, area, inputTitle, inputState, 
inputPlaceholder);
     }
 
     void renderFooter(List<Span> spans) {
@@ -278,7 +251,6 @@ class RefactorPopup {
         }
         switch (phase) {
             case MENU -> {
-                TuiHelper.hint(spans, TuiIcons.HINT_SCROLL, "navigate");
                 TuiHelper.hint(spans, "Enter", "select");
                 TuiHelper.hintLast(spans, "Esc", "close");
             }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java
index 8ca40eaf89be..0d25a4927d81 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java
@@ -890,7 +890,6 @@ class RoutesTab extends AbstractTab {
             hint(spans, "n", "description" + (diagram.isShowDescription() ? " 
[on]" : " [off]"));
         } else {
             hint(spans, "Esc", "back");
-            hint(spans, TuiIcons.HINT_SCROLL, "navigate");
             hint(spans, "Enter", "diagram");
             hint(spans, "s", "sort");
             hint(spans, "n", "description" + (showDescription ? " [on]" : " 
[off]"));
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RunOptionsForm.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RunOptionsForm.java
index 89c5797ebf7b..3a3b2e2629f3 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RunOptionsForm.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RunOptionsForm.java
@@ -31,7 +31,6 @@ import dev.tamboui.widgets.Clear;
 import dev.tamboui.widgets.block.Block;
 import dev.tamboui.widgets.block.BorderType;
 import dev.tamboui.widgets.block.Borders;
-import dev.tamboui.widgets.input.TextInput;
 import dev.tamboui.widgets.input.TextInputState;
 import dev.tamboui.widgets.paragraph.Paragraph;
 
@@ -42,6 +41,8 @@ class RunOptionsForm {
 
     private static final int PAGE_OPTIONS = 0;
     private static final int PAGE_PROPERTIES = 1;
+    /** Fixed height of the options page; the properties page is aligned to 
it. */
+    private static final int PAGE1_HEIGHT = 18;
 
     // Row indices for page 0
     private static final int ROW_NAME = 0;
@@ -216,7 +217,6 @@ class RunOptionsForm {
             hintLast(spans, "Esc", "back");
         } else {
             hint(spans, TuiIcons.KEY_LEFT, "options");
-            hint(spans, TuiIcons.HINT_SCROLL, "navigate");
             hint(spans, "+", "add");
             hint(spans, "Enter", "launch");
             hintLast(spans, "Esc", "back");
@@ -504,10 +504,8 @@ class RunOptionsForm {
 
     private void renderOptionsPage(Frame frame, Rect area) {
         int popupW = Math.min(68, area.width() - 4);
-        int popupH = errorMessage != null ? 19 : 18;
-        int x = area.left() + Math.max(0, (area.width() - popupW) / 2);
-        int y = area.top() + Math.max(0, (area.height() - popupH) / 4);
-        Rect popup = new Rect(x, y, Math.min(popupW, area.width()), 
Math.min(popupH, area.height()));
+        int popupH = errorMessage != null ? PAGE1_HEIGHT + 1 : PAGE1_HEIGHT;
+        Rect popup = DialogHelper.centered(area, popupW, popupH);
 
         frame.renderWidget(Clear.INSTANCE, popup);
 
@@ -610,7 +608,7 @@ class RunOptionsForm {
             rowY++;
             Rect errorArea = new Rect(innerX, rowY, innerW, 1);
             frame.renderWidget(Paragraph.from(Line.from(
-                    Span.styled(TuiIcons.HEALTH_WARN + " " + errorMessage, 
Style.EMPTY.bold()))), errorArea);
+                    Span.styled(TuiIcons.HEALTH_WARN + " " + errorMessage, 
Theme.error().bold()))), errorArea);
         }
     }
 
@@ -618,11 +616,9 @@ class RunOptionsForm {
         int popupW = Math.min(100, area.width() - 4);
         int propCount = properties != null ? properties.size() : 0;
         int popupH = Math.min(propCount + 2, Math.min(20, area.height() - 4));
-        int x = area.left() + Math.max(0, (area.width() - popupW) / 2);
-        // use same y-offset as page 1 (based on page 1's fixed height) so 
both pages align
-        int page1H = 18;
-        int y = area.top() + Math.max(0, (area.height() - page1H) / 4);
-        Rect popup = new Rect(x, y, Math.min(popupW, area.width()), 
Math.min(popupH, area.height()));
+        // use the same y-offset as page 1 (based on page 1's fixed height) so 
both pages align
+        Rect page1 = DialogHelper.centered(area, popupW, PAGE1_HEIGHT);
+        Rect popup = new Rect(page1.x(), page1.y(), page1.width(), 
Math.min(popupH, area.height()));
 
         frame.renderWidget(Clear.INSTANCE, popup);
 
@@ -841,9 +837,7 @@ class RunOptionsForm {
     }
 
     private void renderLabel(Frame frame, int x, int y, int w, String label, 
boolean selected) {
-        Style style = selected ? Style.EMPTY.bold() : Style.EMPTY.dim();
-        Rect labelArea = new Rect(x, y, w, 1);
-        frame.renderWidget(Paragraph.from(Line.from(Span.styled(label, 
style))), labelArea);
+        FormHelper.renderLabel(frame, x, y, w, label, selected);
     }
 
     private void renderTextInput(Frame frame, int x, int y, int w, 
TextInputState state, boolean active) {
@@ -852,24 +846,9 @@ class RunOptionsForm {
 
     private void renderTextInputWithHint(
             Frame frame, int x, int y, int w, TextInputState state, boolean 
active, String hint) {
-        Rect inputArea = new Rect(x, y, w, 1);
-        if (active) {
-            TextInput textInput = TextInput.builder()
-                    .cursorStyle(Style.EMPTY.reversed())
-                    .build();
-            // renderWithCursor (not renderStatefulWidget) so the caret is 
painted on the active field
-            textInput.renderWithCursor(inputArea, frame.buffer(), state, 
frame);
-        } else {
-            String text = state.text();
-            if (text.isEmpty() && hint != null) {
-                frame.renderWidget(Paragraph.from(Line.from(
-                        Span.styled(hint, Style.EMPTY.dim()))), inputArea);
-            } else {
-                Style style = text.isEmpty() ? Style.EMPTY.dim() : Style.EMPTY;
-                frame.renderWidget(Paragraph.from(Line.from(
-                        Span.styled(text.isEmpty() ? "—" : text, style))), 
inputArea);
-            }
-        }
+        // an inactive empty field without a hint shows a dash so the row does 
not look blank
+        String placeholder = hint != null ? hint : (active ? null : "—");
+        FormHelper.renderTextField(frame, new Rect(x, y, w, 1), state, active, 
placeholder);
     }
 
     private void renderCycler(Frame frame, int x, int y, int w, String[] 
labels, int active, boolean selected) {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SendMessagePopup.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SendMessagePopup.java
index 73771658249f..af274382e9c3 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SendMessagePopup.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SendMessagePopup.java
@@ -1125,7 +1125,7 @@ class SendMessagePopup {
             fileBrowser.renderFooter(spans);
             return;
         }
-        hint(spans, "Esc", "back");
+        hint(spans, "Esc", "close");
         hint(spans, "Tab", "fields");
         hint(spans, "F5", "send");
         hint(spans, "Ctrl+F", "file");
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SettingsPopup.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SettingsPopup.java
index 514dc89bbfdf..a4a403695dd7 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SettingsPopup.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SettingsPopup.java
@@ -30,7 +30,6 @@ import dev.tamboui.widgets.Clear;
 import dev.tamboui.widgets.block.Block;
 import dev.tamboui.widgets.block.BorderType;
 import dev.tamboui.widgets.block.Borders;
-import dev.tamboui.widgets.input.TextInput;
 import dev.tamboui.widgets.input.TextInputState;
 import dev.tamboui.widgets.paragraph.Paragraph;
 import org.apache.camel.dsl.jbang.core.commands.LlmClient;
@@ -466,7 +465,6 @@ class SettingsPopup {
     }
 
     void renderFooter(List<Span> spans) {
-        hint(spans, TuiIcons.HINT_SCROLL, "navigate");
         if (selectedRow == ROW_THEME || selectedRow == ROW_START_TAB || 
selectedRow == ROW_SELECT_TAB
                 || selectedRow == ROW_LOG_PIN || selectedRow == ROW_RATE_PER
                 || selectedRow == ROW_CONFIRM_ACTIONS || selectedRow == 
ROW_VALIDATE_ON_SAVE
@@ -546,8 +544,7 @@ class SettingsPopup {
     }
 
     private void renderLabel(Frame frame, int x, int y, int w, String label, 
boolean selected) {
-        Style style = selected ? Style.EMPTY.bold() : Style.EMPTY.dim();
-        frame.renderWidget(Paragraph.from(Line.from(Span.styled(label, 
style))), new Rect(x, y, w, 1));
+        FormHelper.renderLabel(frame, x, y, w, label, selected);
     }
 
     private void renderValue(Frame frame, int x, int y, int w, String text, 
boolean selected) {
@@ -562,20 +559,7 @@ class SettingsPopup {
     private void renderTextInput(
             Frame frame, int x, int y, int w, TextInputState input, boolean 
active,
             String placeholder) {
-        Rect area = new Rect(x, y, w, 1);
-        if (active) {
-            TextInput textInput = TextInput.builder()
-                    .cursorStyle(Style.EMPTY.reversed())
-                    .placeholder(placeholder)
-                    .build();
-            // renderWithCursor (not renderStatefulWidget) so the caret is 
painted on the active field
-            textInput.renderWithCursor(area, frame.buffer(), input, frame);
-        } else {
-            String text = input.text();
-            Style style = text.isEmpty() ? Style.EMPTY.dim() : Style.EMPTY;
-            frame.renderWidget(Paragraph.from(Line.from(
-                    Span.styled(text.isEmpty() ? placeholder : text, style))), 
area);
-        }
+        FormHelper.renderTextField(frame, new Rect(x, y, w, 1), input, active, 
placeholder);
     }
 
     // ---- Test accessors ----
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTab.java
index dfecbff2fb6a..696aed167974 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTab.java
@@ -428,7 +428,6 @@ class SourceTab extends AbstractTab {
                 TuiHelper.hint(spans, "Tab", "files");
             }
         } else {
-            TuiHelper.hint(spans, TuiIcons.HINT_SCROLL, "navigate");
             TuiHelper.hint(spans, "Enter", "open");
             if (currentDir != null && rootDir != null && 
!currentDir.equals(rootDir)) {
                 TuiHelper.hint(spans, "Bksp", "parent");
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceViewer.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceViewer.java
index 87fdee6d8b01..2aa7c2876f55 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceViewer.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceViewer.java
@@ -3002,8 +3002,7 @@ class SourceViewer {
             wrapText(msg, innerW, allLines);
         }
         allLines.add(Line.empty());
-        allLines.add(Line.from(Span.raw("  "),
-                Span.styled("Esc", Style.EMPTY.bold()), Span.raw(" close")));
+        allLines.add(TuiHelper.hintLine("Esc", "close"));
 
         int contentH = allLines.size();
         int popupH = Math.min(contentH + 2, area.height() - 4);
@@ -3017,6 +3016,7 @@ class SourceViewer {
                            + (validationErrors.size() > 1 ? "s" : "") + " ";
         Block block = Block.builder()
                 .borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                .borderStyle(Theme.error())
                 .title(Title.from(Line.from(Span.styled(titleText, 
Theme.error().bold()))))
                 .build();
         frame.renderWidget(block, popup);
@@ -3036,33 +3036,7 @@ class SourceViewer {
     }
 
     private void renderDiscardPopup(Frame frame, Rect area) {
-        int popupW = Math.max(40, Math.min(44, area.width() - 4));
-        popupW = Math.min(popupW, area.width() - 2);
-        int popupH = 6;
-        int x = area.left() + Math.max(0, (area.width() - popupW) / 2);
-        int y = area.top() + Math.max(0, (area.height() - popupH) / 2);
-        Rect popup = new Rect(x, y, popupW, popupH);
-
-        frame.renderWidget(Clear.INSTANCE, popup);
-
-        Block block = Block.builder()
-                .borderType(BorderType.ROUNDED).borders(Borders.ALL)
-                .borderStyle(Theme.warning())
-                .title(Title.from(Line.from(Span.styled(" Discard Changes? ", 
Theme.warning().bold()))))
-                .build();
-        frame.renderWidget(block, popup);
-        Rect inner = block.inner(popup);
-
-        frame.renderWidget(
-                Paragraph.builder().centered().text(Text.from(
-                        Line.empty(),
-                        Line.from(Span.raw("Unsaved changes will be lost.")),
-                        Line.empty(),
-                        Line.from(
-                                Span.styled("Enter", Style.EMPTY.bold()), 
Span.raw(" confirm    "),
-                                Span.styled("Esc", Style.EMPTY.bold()), 
Span.raw(" cancel"))))
-                        .build(),
-                inner);
+        DialogHelper.renderConfirm(frame, area, "Discard Changes?", "Unsaved 
changes will be lost.", false);
     }
 
     private static void wrapText(String text, int width, List<Line> out) {
@@ -3081,14 +3055,17 @@ class SourceViewer {
     }
 
     void renderFooter(List<Span> spans) {
+        if (pendingDiscard) {
+            TuiHelper.hint(spans, "Enter", "confirm");
+            TuiHelper.hintLast(spans, "Esc", "cancel");
+            return;
+        }
         if (editMode && validationErrors != null) {
-            TuiHelper.hint(spans, TuiIcons.HINT_SCROLL, "scroll");
             TuiHelper.hintLast(spans, "Esc", "close");
             return;
         }
         if (editMode && diffOverlay) {
             TuiHelper.hint(spans, "Esc/F7", "close diff");
-            TuiHelper.hint(spans, TuiIcons.HINT_SCROLL, "scroll");
             return;
         }
         if (editMode) {
@@ -3121,7 +3098,6 @@ class SourceViewer {
         }
         if (markdownMode) {
             TuiHelper.hint(spans, "Esc/c", "close");
-            TuiHelper.hint(spans, TuiIcons.HINT_SCROLL, "scroll");
             TuiHelper.hint(spans, "Space", "format");
             TuiHelper.hint(spans, "PgUp/PgDn", "page");
             if (isEditable()) {
@@ -3141,7 +3117,6 @@ class SourceViewer {
         if (isEditable()) {
             TuiHelper.hint(spans, "F4", "edit");
         }
-        TuiHelper.hint(spans, TuiIcons.HINT_SCROLL, "navigate");
         if (isMarkdownFile || currentRouteId != null) {
             TuiHelper.hint(spans, "Space", "format");
         }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SpansTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SpansTab.java
index d3c293fa5b47..d1c53ba727ab 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SpansTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SpansTab.java
@@ -646,7 +646,6 @@ class SpansTab extends AbstractTab {
             hint(spans, "F5", "refresh");
             hint(spans, "c", camelOnly ? "camel-only [on]" : "camel-only 
[off]");
             hint(spans, "p", showProcessors ? "processors [on]" : "processors 
[off]");
-            hint(spans, TuiIcons.HINT_SCROLL, "navigate");
             hintLast(spans, "PgUp/Dn", "page");
         } else if (filterInputActive) {
             spans.add(Span.styled(" /", Theme.label().bold()));
@@ -663,7 +662,6 @@ class SpansTab extends AbstractTab {
             } else {
                 hint(spans, "/", "filter");
             }
-            hintLast(spans, TuiIcons.HINT_SCROLL, "navigate");
         }
     }
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SqlQueryTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SqlQueryTab.java
index 1556e821c752..3405ab1bc418 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SqlQueryTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SqlQueryTab.java
@@ -807,13 +807,8 @@ class SqlQueryTab extends AbstractTab {
         int footerY = inner.top() + visibleRows + 1;
         if (footerY < popup.bottom() - 1) {
             Rect footerArea = new Rect(inner.left(), footerY, inner.width(), 
1);
-            frame.renderWidget(Paragraph.from(Line.from(
-                    Span.styled(" F5", Theme.label().bold()),
-                    Span.styled("=Save  ", Theme.muted()),
-                    Span.styled("Esc", Theme.label().bold()),
-                    Span.styled("=Cancel  ", Theme.muted()),
-                    Span.styled("*", Theme.muted()),
-                    Span.styled("=Primary Key", Theme.muted()))), footerArea);
+            frame.renderWidget(Paragraph.from(
+                    TuiHelper.hintLine("F5", "save", "Esc", "cancel", "*", 
"primary key")), footerArea);
         }
     }
 
@@ -837,7 +832,6 @@ class SqlQueryTab extends AbstractTab {
         } else {
             hint(spans, "Esc", "back");
             hint(spans, "Tab", "input");
-            hint(spans, TuiIcons.HINT_SCROLL, "navigate");
             if (isEditable()) {
                 hint(spans, "F4", "edit");
             }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SqlTraceTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SqlTraceTab.java
index 738a1c501fe5..15fad69d4692 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SqlTraceTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SqlTraceTab.java
@@ -387,7 +387,6 @@ class SqlTraceTab extends AbstractTableTab {
     @Override
     public void renderFooter(List<Span> spans) {
         hint(spans, "Esc", "back");
-        hint(spans, TuiIcons.HINT_SCROLL, "navigate");
         hint(spans, "Home/End", "top/end");
         hint(spans, "PgUp/Dn", "detail");
         hint(spans, "F4", "edit SQL");
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StartupTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StartupTab.java
index 60d04399d968..25a6b2428528 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StartupTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StartupTab.java
@@ -255,7 +255,6 @@ class StartupTab extends AbstractTab {
     @Override
     public void renderFooter(List<Span> spans) {
         hint(spans, "Esc", "back");
-        hint(spans, TuiIcons.HINT_SCROLL, "scroll");
         hintLast(spans, "PgUp/Dn", "page");
     }
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ThemePopup.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ThemePopup.java
index 88ac5babaf0e..b6ad559aae01 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ThemePopup.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ThemePopup.java
@@ -180,7 +180,7 @@ class ThemePopup {
     void renderFooter(List<Span> spans) {
         hint(spans, TuiIcons.HINT_SCROLL, "preview");
         hint(spans, "Enter", "apply");
-        hintLast(spans, "Esc", "back");
+        hintLast(spans, "Esc", "cancel");
     }
 
     List<String> getLabels() {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ThreadsTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ThreadsTab.java
index ba959cc04d49..ba1e5e66b687 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ThreadsTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ThreadsTab.java
@@ -297,7 +297,6 @@ class ThreadsTab extends AbstractTableTab {
         hint(spans, "s", "sort");
         hint(spans, "f", "filter [" + FILTER_LABELS[filter] + "]");
         hint(spans, "Tab", detailFocused ? "table" : "trace");
-        hint(spans, TuiIcons.HINT_SCROLL, "navigate");
         hintLast(spans, "PgUp/Dn", "scroll");
     }
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TransformersTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TransformersTab.java
index 4ab4369e7e89..c7487bee19b4 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TransformersTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TransformersTab.java
@@ -129,7 +129,6 @@ class TransformersTab extends AbstractTableTab {
     @Override
     public void renderFooter(List<Span> spans) {
         hint(spans, "Esc", "back");
-        hint(spans, TuiIcons.HINT_SCROLL, "navigate");
         hintLast(spans, "s", "sort");
     }
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiHelper.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiHelper.java
index fb6a4d1ff941..6f2b6305e353 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiHelper.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiHelper.java
@@ -452,6 +452,27 @@ final class TuiHelper {
         spans.add(Span.raw(" " + label));
     }
 
+    /**
+     * Builds one line of key hints in the footer style, e.g. {@code 
hintLine("Enter", "confirm", "Esc", "cancel")}. Use
+     * this for hints drawn inside a dialog so they match the footer bar.
+     *
+     * @param keyLabelPairs alternating key and label, must have an even length
+     */
+    static Line hintLine(String... keyLabelPairs) {
+        if (keyLabelPairs.length % 2 != 0) {
+            throw new IllegalArgumentException("keyLabelPairs must be 
key/label pairs");
+        }
+        List<Span> spans = new ArrayList<>();
+        for (int i = 0; i < keyLabelPairs.length; i += 2) {
+            if (i + 2 < keyLabelPairs.length) {
+                hint(spans, keyLabelPairs[i], keyLabelPairs[i + 1]);
+            } else {
+                hintLast(spans, keyLabelPairs[i], keyLabelPairs[i + 1]);
+            }
+        }
+        return Line.from(spans);
+    }
+
     static boolean contains(Rect rect, int x, int y) {
         return rect != null
                 && x >= rect.x() && x < rect.x() + rect.width()
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TypeConvertersTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TypeConvertersTab.java
index 2d6045d2329a..0a067fb8d910 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TypeConvertersTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TypeConvertersTab.java
@@ -207,7 +207,6 @@ class TypeConvertersTab extends AbstractTableTab {
         } else {
             hint(spans, "/", "filter");
         }
-        hint(spans, TuiIcons.HINT_SCROLL, "navigate");
         hintLast(spans, "s", "sort");
     }
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/BeansTabRenderTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/BeansTabRenderTest.java
index 428a4ca38c5c..422feb74d9d4 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/BeansTabRenderTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/BeansTabRenderTest.java
@@ -87,8 +87,7 @@ class BeansTabRenderTest {
         assertTrue(footer.contains("Esc"), "Footer should contain Esc hint");
         assertTrue(footer.contains("scope"), "Footer should contain scope 
hint");
         assertTrue(footer.contains("filter"), "Footer should contain filter 
hint");
-        assertTrue(footer.contains("navigate"), "Footer should contain 
navigate hint");
-        assertTrue(footer.contains("scroll"), "Footer should contain scroll 
hint");
+        assertTrue(footer.contains("scroll"), "Footer should contain PgUp/Dn 
scroll hint");
     }
 
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/DialogHelperTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/DialogHelperTest.java
new file mode 100644
index 000000000000..c97c809c8899
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/DialogHelperTest.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.jbang.core.commands.tui;
+
+import dev.tamboui.buffer.Buffer;
+import dev.tamboui.layout.Rect;
+import dev.tamboui.terminal.Frame;
+import dev.tamboui.text.Line;
+import dev.tamboui.text.Span;
+import dev.tamboui.widgets.input.TextInputState;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * Tests for the shared dialog building blocks in {@link DialogHelper} and the 
hint helpers in {@link TuiHelper}.
+ */
+class DialogHelperTest {
+
+    private static final Rect AREA = new Rect(0, 0, 120, 40);
+
+    @Test
+    void centeredPlacesDialogInUpperThird() {
+        Rect r = DialogHelper.centered(AREA, 40, 10);
+        assertThat(r.x()).isEqualTo(40);
+        assertThat(r.y()).isEqualTo(10);
+        assertThat(r.width()).isEqualTo(40);
+        assertThat(r.height()).isEqualTo(10);
+    }
+
+    @Test
+    void centeredClampsToArea() {
+        Rect r = DialogHelper.centered(new Rect(5, 5, 20, 8), 40, 10);
+        assertThat(r.x()).isEqualTo(5);
+        assertThat(r.y()).isEqualTo(5);
+        assertThat(r.width()).isEqualTo(20);
+        assertThat(r.height()).isEqualTo(8);
+    }
+
+    @Test
+    void clampWidthKeepsMarginAndBounds() {
+        assertThat(DialogHelper.clampWidth(AREA, 34, 20)).isEqualTo(34);
+        assertThat(DialogHelper.clampWidth(AREA, 34, 60)).isEqualTo(60);
+        assertThat(DialogHelper.clampWidth(AREA, 34, 500)).isEqualTo(116);
+        // narrower than the minimum: shrink to fit rather than overflow
+        assertThat(DialogHelper.clampWidth(new Rect(0, 0, 30, 10), 34, 
60)).isEqualTo(28);
+    }
+
+    @Test
+    void confirmDialogShowsTitleMessageAndFooterStyleHints() {
+        Buffer buffer = Buffer.empty(AREA);
+        Frame frame = Frame.forTesting(buffer);
+
+        Rect popup = DialogHelper.renderConfirm(frame, AREA, "Confirm Quit", 
"Quit the TUI?", false);
+
+        String rendered = HealthTabRenderTest.bufferToString(buffer);
+        assertThat(rendered).contains(" Confirm Quit ");
+        assertThat(rendered).contains("Quit the TUI?");
+        assertThat(rendered).contains(" Enter  confirm");
+        assertThat(rendered).contains(" Esc  cancel");
+        assertThat(popup.height()).isEqualTo(DialogHelper.CONFIRM_HEIGHT);
+        assertThat(popup.width()).isGreaterThanOrEqualTo(34);
+    }
+
+    @Test
+    void confirmDialogCanUseCustomAcceptKey() {
+        Buffer buffer = Buffer.empty(AREA);
+        Frame frame = Frame.forTesting(buffer);
+
+        DialogHelper.renderConfirm(frame, AREA, "Delete file?", "Delete 
foo.yaml?", true, "y", "delete");
+
+        String rendered = HealthTabRenderTest.bufferToString(buffer);
+        assertThat(rendered).contains(" y  delete");
+        assertThat(rendered).contains(" Esc  cancel");
+        assertThat(rendered).doesNotContain("Enter");
+    }
+
+    @Test
+    void inputDialogShowsTitleAndPlaceholder() {
+        Buffer buffer = Buffer.empty(AREA);
+        Frame frame = Frame.forTesting(buffer);
+
+        Rect popup = DialogHelper.renderInputDialog(frame, AREA, "New File", 
new TextInputState(), "name");
+
+        String rendered = HealthTabRenderTest.bufferToString(buffer);
+        assertThat(rendered).contains(" New File ");
+        assertThat(rendered).contains("name");
+        assertThat(popup.height()).isEqualTo(DialogHelper.INPUT_HEIGHT);
+    }
+
+    @Test
+    void hintLineMatchesFooterHints() {
+        Line line = TuiHelper.hintLine("Enter", "confirm", "Esc", "cancel");
+        assertThat(line.spans()).extracting(Span::content)
+                .containsExactly(" Enter ", " confirm  ", " Esc ", " cancel");
+    }
+
+    @Test
+    void hintLineRejectsOddArguments() {
+        assertThatThrownBy(() -> TuiHelper.hintLine("Enter"))
+                .isInstanceOf(IllegalArgumentException.class);
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/DoctorPopupWrapTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/DoctorPopupWrapTest.java
new file mode 100644
index 000000000000..3477ec42db75
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/DoctorPopupWrapTest.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.jbang.core.commands.tui;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests the word wrapping used for the detail rows of the Doctor popup.
+ */
+class DoctorPopupWrapTest {
+
+    @Test
+    void wrapsOnWordBoundaries() {
+        List<String> lines = DoctorPopup.wrapWords(
+                "Set ANTHROPIC_API_KEY, AZURE_OPENAI_*, GEMINI_API_KEY, 
OPENAI_API_KEY, WATSONX_APIKEY, or start Ollama",
+                41);
+        assertThat(lines).containsExactly(
+                "Set ANTHROPIC_API_KEY, AZURE_OPENAI_*,",
+                "GEMINI_API_KEY, OPENAI_API_KEY,",
+                "WATSONX_APIKEY, or start Ollama");
+        assertThat(lines).allSatisfy(l -> 
assertThat(l.length()).isLessThanOrEqualTo(41));
+    }
+
+    @Test
+    void shortTextStaysOnOneLine() {
+        assertThat(DoctorPopup.wrapWords("No AI client connected", 
41)).containsExactly("No AI client connected");
+    }
+
+    @Test
+    void longWordIsKeptWhole() {
+        assertThat(DoctorPopup.wrapWords("a bbbbbbbbbbbbbbbbbbbb c", 
10)).containsExactly("a", "bbbbbbbbbbbbbbbbbbbb", "c");
+    }
+
+    @Test
+    void blankTextProducesNoRows() {
+        assertThat(DoctorPopup.wrapWords("  ", 41)).isEmpty();
+        assertThat(DoctorPopup.wrapWords(null, 41)).isEmpty();
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTabRenderTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTabRenderTest.java
index c16b38d1395b..2d83070df68b 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTabRenderTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTabRenderTest.java
@@ -209,7 +209,6 @@ class OverviewTabRenderTest {
                 .reduce("", String::concat);
 
         assertTrue(footer.contains("sort"), "Footer should contain sort hint");
-        assertTrue(footer.contains("navigate"), "Footer should contain 
navigate hint");
     }
 
     @Test
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/PopupManagerTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/PopupManagerTest.java
index c95c96e6008f..b648b287ea3a 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/PopupManagerTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/PopupManagerTest.java
@@ -20,6 +20,7 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicReference;
 
+import dev.tamboui.tui.event.KeyCode;
 import dev.tamboui.tui.event.KeyEvent;
 import dev.tamboui.tui.event.KeyModifiers;
 import org.junit.jupiter.api.BeforeEach;
@@ -143,4 +144,48 @@ class PopupManagerTest {
         // 'z' matches nothing
         assertArrayEquals(new int[] { -1, 0 }, 
popupManager.morePopupShortcut(KeyEvent.ofChar('z', KeyModifiers.NONE)));
     }
+
+    // ---- Confirm dialog key contract: Enter accepts, Esc cancels, anything 
else is swallowed ----
+
+    @Test
+    void confirmRunsCallbackOnEnter() {
+        boolean[] ran = { false };
+        popupManager.showConfirm("Confirm Quit", " Quit? ", () -> ran[0] = 
true);
+        assertTrue(popupManager.isConfirmVisible());
+
+        popupManager.handleKeyEvent(KeyEvent.ofKey(KeyCode.ENTER, 
KeyModifiers.NONE), 0, 2);
+        assertTrue(ran[0], "Enter must run the confirm callback");
+        assertFalse(popupManager.isConfirmVisible(), "Confirm should close 
after Enter");
+    }
+
+    @Test
+    void confirmCancelsOnEscapeWithoutRunningCallback() {
+        boolean[] ran = { false };
+        popupManager.showConfirm("Confirm Quit", " Quit? ", () -> ran[0] = 
true);
+
+        popupManager.handleKeyEvent(KeyEvent.ofKey(KeyCode.ESCAPE, 
KeyModifiers.NONE), 0, 2);
+        assertFalse(ran[0], "Esc must not run the confirm callback");
+        assertFalse(popupManager.isConfirmVisible(), "Confirm should close 
after Esc");
+    }
+
+    @Test
+    void confirmIgnoresOtherKeys() {
+        boolean[] ran = { false };
+        popupManager.showConfirm("Confirm Quit", " Quit? ", () -> ran[0] = 
true);
+
+        assertTrue(popupManager.handleKeyEvent(KeyEvent.ofChar('x'), 0, 2), 
"key is swallowed by the modal");
+        assertFalse(ran[0], "a stray key must not confirm");
+        assertTrue(popupManager.isConfirmVisible(), "a stray key must not 
dismiss the confirm");
+    }
+
+    @Test
+    void killConfirmIgnoresOtherKeysAndCancelsOnEscape() {
+        popupManager.showKillConfirm();
+
+        popupManager.handleKeyEvent(KeyEvent.ofChar('x'), 0, 2);
+        assertTrue(popupManager.isKillConfirmVisible(), "a stray key must not 
dismiss the kill confirm");
+
+        popupManager.handleKeyEvent(KeyEvent.ofKey(KeyCode.ESCAPE, 
KeyModifiers.NONE), 0, 2);
+        assertFalse(popupManager.isKillConfirmVisible(), "Esc cancels the kill 
confirm");
+    }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ProcessControlPopupTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ProcessControlPopupTest.java
new file mode 100644
index 000000000000..6c75b2dc2517
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ProcessControlPopupTest.java
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.jbang.core.commands.tui;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+import dev.tamboui.tui.event.KeyCode;
+import dev.tamboui.tui.event.KeyEvent;
+import dev.tamboui.tui.event.KeyModifiers;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests that the F10 process-control popup gates Stop, Restart and Kill 
behind a confirm dialog when confirmations are
+ * enabled, and runs them directly when they are disabled.
+ */
+class ProcessControlPopupTest {
+
+    private MonitorContext ctx;
+    private ProcessControlPopup popup;
+    private final List<String> calls = new ArrayList<>();
+    private final List<Runnable> pendingConfirms = new ArrayList<>();
+
+    @BeforeEach
+    void setUp() {
+        IntegrationInfo info = new IntegrationInfo();
+        info.pid = "4242";
+        info.name = "my-app";
+
+        ctx = new MonitorContext(new AtomicReference<>(List.of(info)), new 
AtomicReference<>(List.of()));
+        ctx.selectedPid = "4242";
+
+        popup = new ProcessControlPopup(ctx);
+        popup.setActions(new ProcessControlPopup.ControlActions() {
+            @Override
+            public void sendRouteCommand(String pid, String routeId, String 
command) {
+                calls.add("route:" + command);
+            }
+
+            @Override
+            public void stopSelectedProcess(boolean forceKill) {
+                calls.add(forceKill ? "kill" : "stop");
+            }
+
+            @Override
+            public void restartSelectedProcess() {
+                calls.add("restart");
+            }
+
+            @Override
+            public void showKillConfirm() {
+                calls.add("confirm:kill");
+            }
+
+            @Override
+            public void showConfirm(String title, String message, Runnable 
onConfirm) {
+                calls.add("confirm:" + title + ":" + message.trim());
+                pendingConfirms.add(onConfirm);
+            }
+
+            @Override
+            public void onRunPhantom(IntegrationInfo phantom) {
+                calls.add("run-phantom");
+            }
+
+            @Override
+            public void onStopAll() {
+                calls.add("stop-all");
+            }
+
+            @Override
+            public boolean hasRunningProcesses() {
+                return false;
+            }
+        });
+    }
+
+    // A non-phantom integration without routes lists: Restart (0), Stop (1), 
Kill (2)
+
+    private void select(int index) {
+        popup.open();
+        assertThat(popup.isVisible()).isTrue();
+        for (int i = 0; i < index; i++) {
+            popup.handleKeyEvent(KeyEvent.ofKey(KeyCode.DOWN, 
KeyModifiers.NONE));
+        }
+        popup.handleKeyEvent(KeyEvent.ofKey(KeyCode.ENTER, KeyModifiers.NONE));
+        assertThat(popup.isVisible()).isFalse();
+    }
+
+    @Test
+    void killAsksForConfirmationWhenEnabled() {
+        ctx.confirmActions = true;
+        select(2);
+        assertThat(calls).containsExactly("confirm:kill");
+    }
+
+    @Test
+    void killRunsDirectlyWhenConfirmationsDisabled() {
+        ctx.confirmActions = false;
+        select(2);
+        assertThat(calls).containsExactly("kill");
+    }
+
+    @Test
+    void stopAsksForConfirmationAndRunsOnAccept() {
+        ctx.confirmActions = true;
+        select(1);
+        assertThat(calls).containsExactly("confirm:Confirm Stop:Stop my-app 
(PID: 4242)?");
+
+        pendingConfirms.get(0).run();
+        assertThat(calls).containsExactly("confirm:Confirm Stop:Stop my-app 
(PID: 4242)?", "stop");
+    }
+
+    @Test
+    void restartAsksForConfirmationAndRunsOnAccept() {
+        ctx.confirmActions = true;
+        select(0);
+        assertThat(calls).containsExactly("confirm:Confirm Restart:Restart 
my-app (PID: 4242)?");
+
+        pendingConfirms.get(0).run();
+        assertThat(calls).last().isEqualTo("restart");
+    }
+
+    @Test
+    void stopAndRestartRunDirectlyWhenConfirmationsDisabled() {
+        ctx.confirmActions = false;
+        select(1);
+        select(0);
+        assertThat(calls).containsExactly("stop", "restart");
+    }
+
+    @Test
+    void escapeClosesWithoutRunningAnything() {
+        ctx.confirmActions = false;
+        popup.open();
+        popup.handleKeyEvent(KeyEvent.ofKey(KeyCode.ESCAPE, 
KeyModifiers.NONE));
+        assertThat(popup.isVisible()).isFalse();
+        assertThat(calls).isEmpty();
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ProcessTabRenderTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ProcessTabRenderTest.java
index c068635f6354..d521d7402208 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ProcessTabRenderTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ProcessTabRenderTest.java
@@ -98,7 +98,6 @@ class ProcessTabRenderTest {
                 .map(Span::content)
                 .reduce("", String::concat);
 
-        assertTrue(footer.contains("scroll"), "Footer should contain scroll 
hint");
         assertTrue(footer.contains("wrap"), "Footer should contain wrap hint");
         assertTrue(footer.contains("Esc"), "Footer should contain Esc hint");
     }

Reply via email to