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 7ece2ccd99aa camel-jbang-tui: Add confirm setting for destructive
actions
7ece2ccd99aa is described below
commit 7ece2ccd99aa462bb7d24356ad6deda2a24b5ad7
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Jul 28 22:41:20 2026 +0200
camel-jbang-tui: Add confirm setting for destructive actions
camel-jbang-tui: Add gruvbox-light theme and polish confirm dialogs
camel-jbang-tui: Default confirm to on, add help and docs
camel-jbang-tui: Fix SettingsPopup tests for confirm row shift
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
---
.../modules/ROOT/pages/camel-jbang-tui.adoc | 7 +-
.../dsl/jbang/core/commands/tui/CamelMonitor.java | 19 +++++-
.../jbang/core/commands/tui/MonitorContext.java | 1 +
.../dsl/jbang/core/commands/tui/OverviewTab.java | 39 +++++++++++-
.../dsl/jbang/core/commands/tui/PopupManager.java | 74 +++++++++++++++++++++-
.../dsl/jbang/core/commands/tui/SettingsPopup.java | 39 +++++++++---
.../dsl/jbang/core/commands/tui/TuiSettings.java | 16 +++++
.../main/resources/tui/themes/gruvbox-light.tcss | 10 +--
.../jbang/core/commands/tui/SettingsPopupTest.java | 20 +++---
9 files changed, 194 insertions(+), 31 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 076d2375ad64..f6cfc12cae74 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-tui.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-tui.adoc
@@ -475,6 +475,9 @@ Open the *F2* actions menu and choose *Settings...* to
change TUI preferences in
chosen. Defaults to *Overview*.
* *Select Tab* -- the tab to switch to when selecting an integration from the
Overview tab.
Defaults to *Log*.
+* *Confirm* -- whether destructive actions (quit, stop, restart, stop/start
routes) show a
+ confirmation dialog before executing. Defaults to *on*. Kill (*X*) always
confirms regardless
+ of this setting.
* *Default Folder* -- the folder pre-filled in *Run from Folder*. The most
recently used folder
still takes precedence; this default is used only when there is no
remembered folder.
@@ -482,8 +485,8 @@ Use *↑*/*↓* to move between rows, *Space* (or *←*/*→*) to
cycle the them
edit the default folder, *Enter* to save, and *Esc* to cancel.
Settings are stored under `camel.tui.*` keys (`camel.tui.theme`,
`camel.tui.startTab`,
-`camel.tui.selectTab`, `camel.tui.defaultFolder`, `camel.tui.shell.history`,
-`camel.tui.ai.promptHistory`) in the Camel CLI configuration file. Each key is
read from and
+`camel.tui.selectTab`, `camel.tui.confirmActions`, `camel.tui.defaultFolder`,
+`camel.tui.shell.history`, `camel.tui.ai.promptHistory`) in the Camel CLI
configuration file. Each key is read from and
written back to the file where it currently lives: a key present in the local
`./camel-cli.properties` is treated as a project-level override and stays
local, while every
other key defaults to the global `~/.camel-cli.properties`. This means a
project can
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 cf2eee2e78ca..a0d395a7f24a 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
@@ -418,6 +418,11 @@ public class CamelMonitor extends CamelCommand {
public void openFilesPopup() {
CamelMonitor.this.openFilesPopup();
}
+
+ @Override
+ public void showConfirm(String title, String message, Runnable
onConfirm) {
+ popupManager.showConfirm(title, message, onConfirm);
+ }
});
// Initial data load (synchronous before TUI starts)
@@ -533,6 +538,7 @@ public class CamelMonitor extends CamelCommand {
applyStartingTab();
applyLogPin();
applyRatePer();
+ applyConfirmActions();
// Intercept Ctrl+C: quit the TUI cleanly instead of letting
// the JVM tear down the classloader while we're still running
Signal.handle(new Signal("INT"), sig -> tui.quit());
@@ -604,6 +610,10 @@ public class CamelMonitor extends CamelCommand {
ctx.ratePerMinute = "minutes".equals(ratePer);
}
+ private void applyConfirmActions() {
+ ctx.confirmActions = TuiSettings.load().isConfirmActions();
+ }
+
// ---- Event Handling ----
private boolean handleEvent(Event event, TuiRunner runner) {
@@ -742,7 +752,11 @@ public class CamelMonitor extends CamelCommand {
|| beanFilterActive || classpathFilterActive ||
mavenDepsFilterActive || sqlInputActive
|| catalogFilterActive;
if (!textEditing && (ke.isCharIgnoreCase('q') || ke.isCtrlC())) {
- runner.quit();
+ if (!ke.isCtrlC() && ctx.confirmActions) {
+ popupManager.showConfirm("Confirm Quit", " Quit the TUI? ", ()
-> runner.quit());
+ } else {
+ runner.quit();
+ }
return true;
}
if (ke.isCtrlC()) {
@@ -1348,6 +1362,9 @@ public class CamelMonitor extends CamelCommand {
if (popupManager.isKillConfirmVisible()) {
popupManager.renderKillConfirm(frame, contentArea);
}
+ if (popupManager.isConfirmVisible()) {
+ popupManager.renderConfirm(frame, contentArea);
+ }
actionsPopup.render(frame, contentArea);
if (captionOverlay.isCaptionVisible()) {
captionOverlay.render(frame, contentArea);
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MonitorContext.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MonitorContext.java
index 2b200b23ddb0..91fbb2b60213 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MonitorContext.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MonitorContext.java
@@ -51,6 +51,7 @@ class MonitorContext {
int logPinPercent;
boolean logPinVisible;
boolean ratePerMinute;
+ boolean confirmActions;
BiConsumer<String, Boolean> notificationCallback;
BiConsumer<String, String> openMarkdownCallback;
OpenOptionsCallback openOptionsCallback;
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 1379044a2ea5..21dc73241c74 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
@@ -72,6 +72,8 @@ class OverviewTab extends AbstractTab {
void openDoc(IntegrationInfo info);
void openFilesPopup();
+
+ void showConfirm(String title, String message, Runnable onConfirm);
}
private static final long VANISH_DURATION_MS = 6000;
@@ -196,12 +198,25 @@ class OverviewTab extends AbstractTab {
IntegrationInfo selInfo = ctx.findSelectedIntegration();
if (selInfo != null) {
String cmd = selInfo.routeStarted > 0 ? "stop" : "start";
- actions.sendRouteCommand(ctx.selectedPid, "*", cmd);
+ if (ctx.confirmActions) {
+ String label = selInfo.routeStarted > 0 ? "Stop" :
"Start";
+ actions.showConfirm("Confirm " + label + " Routes",
+ " " + label + " all routes for " +
ctx.selectedName() + "? ",
+ () ->
actions.sendRouteCommand(ctx.selectedPid, "*", cmd));
+ } else {
+ actions.sendRouteCommand(ctx.selectedPid, "*", cmd);
+ }
}
return true;
}
if (ke.isChar('x') && ctx.selectedPid != null) {
- actions.stopSelectedProcess(false);
+ if (ctx.confirmActions) {
+ actions.showConfirm("Confirm Stop",
+ " Stop " + ctx.selectedName() + " (PID: " +
ctx.selectedPid + ")? ",
+ () -> actions.stopSelectedProcess(false));
+ } else {
+ actions.stopSelectedProcess(false);
+ }
return true;
}
if (ke.isChar('X') && ctx.selectedPid != null) {
@@ -209,7 +224,13 @@ class OverviewTab extends AbstractTab {
return true;
}
if (ke.isChar('r') && ctx.selectedPid != null &&
!ctx.isInfraSelected()) {
- actions.restartSelectedProcess();
+ if (ctx.confirmActions) {
+ actions.showConfirm("Confirm Restart",
+ " Restart " + ctx.selectedName() + " (PID: " +
ctx.selectedPid + ")? ",
+ () -> actions.restartSelectedProcess());
+ } else {
+ actions.restartSelectedProcess();
+ }
return true;
}
if (ke.isChar('f') && ctx.selectedPid != null &&
!ctx.isInfraSelected()) {
@@ -1374,6 +1395,18 @@ class OverviewTab extends AbstractTab {
- `S` — reverse sort order
- `F2` — actions menu (includes theme toggle, go to tab, etc.)
- `F3` — switch integration
+
+ ## Process Control
+
+ - `p` — stop or start all routes for the selected integration
+ - `x` — stop the selected integration (graceful shutdown)
+ - `X` — kill the selected integration (force terminate, always
confirms)
+ - `r` — restart the selected integration
+ - `q` — quit the TUI
+
+ By default, these actions show a confirmation dialog before
executing.
+ You can turn this off in Settings (`F2` → `Settings...` →
`Confirm`).
+ Kill (`X`) always confirms regardless of this setting.
""";
}
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 f425cb17aebc..4ea5fad6720d 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
@@ -86,6 +86,12 @@ class PopupManager {
// Kill confirm
private boolean showKillConfirm;
+ // Generic confirm dialog
+ private boolean showConfirm;
+ private String confirmTitle;
+ private String confirmMessage;
+ private Runnable confirmCallback;
+
// Last rendered popup rects for mouse hit-testing
private Rect lastMorePopupRect;
private Rect lastSwitchPopupRect;
@@ -156,7 +162,7 @@ class PopupManager {
// ---- State queries ----
boolean isAnyPopupVisible() {
- return showSwitchPopup || showMorePopup || showKillConfirm ||
filesBrowser.isVisible();
+ return showSwitchPopup || showMorePopup || showKillConfirm ||
showConfirm || filesBrowser.isVisible();
}
boolean isSwitchPopupVisible() {
@@ -171,6 +177,10 @@ class PopupManager {
return showKillConfirm;
}
+ boolean isConfirmVisible() {
+ return showConfirm;
+ }
+
int getLastMoreSelection() {
return lastMoreSelection;
}
@@ -212,6 +222,8 @@ class PopupManager {
showMorePopup = false;
showSwitchPopup = false;
showKillConfirm = false;
+ showConfirm = false;
+ confirmCallback = null;
filesBrowser.reset();
}
@@ -223,6 +235,13 @@ class PopupManager {
showKillConfirm = true;
}
+ void showConfirm(String title, String message, Runnable onConfirm) {
+ this.confirmTitle = title;
+ this.confirmMessage = message;
+ this.confirmCallback = onConfirm;
+ this.showConfirm = true;
+ }
+
void selectMorePopupEntry(int moreIndex) {
int visualIndex = moreToVisualIndex(moreIndex);
morePopupState.select(visualIndex);
@@ -244,6 +263,9 @@ class PopupManager {
if (showKillConfirm) {
return handleKillConfirmKeys(ke);
}
+ if (showConfirm) {
+ return handleConfirmKeys(ke);
+ }
return false;
}
@@ -398,6 +420,21 @@ class PopupManager {
return true;
}
+ private boolean handleConfirmKeys(KeyEvent ke) {
+ if (ke.isConfirm()) {
+ showConfirm = false;
+ Runnable cb = confirmCallback;
+ confirmCallback = null;
+ if (cb != null) {
+ cb.run();
+ }
+ } else {
+ showConfirm = false;
+ confirmCallback = null;
+ }
+ return true;
+ }
+
// ---- Mouse handling ----
boolean handleMouseEvent(MouseEvent me, int selectedTab, int tabLog) {
@@ -663,7 +700,7 @@ class PopupManager {
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) / 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);
@@ -690,6 +727,39 @@ class PopupManager {
inner);
}
+ 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()
+ .text(Text.from(
+ Line.from(Span.raw("")),
+ Line.from(Span.styled(msg,
Theme.warning().bold())),
+ Line.from(Span.raw("")),
+ Line.from(
+ Span.raw(" "),
+ Span.styled("Enter",
Style.EMPTY.bold()),
+ Span.raw(" confirm "),
+ Span.styled("Esc", Style.EMPTY.bold()),
+ Span.raw(" cancel"))))
+ .build(),
+ inner);
+ }
+
int[] morePopupShortcut(KeyEvent ke) {
List<TabRegistry.MoreTab> tabs = moreTabsSupplier.get();
IntegrationInfo info = ctx.findSelectedIntegration();
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 bf0bbe231cb9..745c4abd5618 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
@@ -51,15 +51,16 @@ class SettingsPopup {
private static final int ROW_SELECT_TAB = 2;
private static final int ROW_LOG_PIN = 3;
private static final int ROW_RATE_PER = 4;
- private static final int ROW_FOLDER = 5;
- private static final int ROW_PROXY_HOST = 6;
- private static final int ROW_PROXY_PORT = 7;
- private static final int ROW_SHELL_HISTORY = 8;
- private static final int ROW_AI_PROVIDER = 9;
- private static final int ROW_AI_MODEL = 10;
- private static final int ROW_AI_URL = 11;
- private static final int ROW_AI_PROMPT_HISTORY = 12;
- private static final int ROW_COUNT = 13;
+ private static final int ROW_CONFIRM_ACTIONS = 5;
+ private static final int ROW_FOLDER = 6;
+ private static final int ROW_PROXY_HOST = 7;
+ private static final int ROW_PROXY_PORT = 8;
+ private static final int ROW_SHELL_HISTORY = 9;
+ private static final int ROW_AI_PROVIDER = 10;
+ private static final int ROW_AI_MODEL = 11;
+ private static final int ROW_AI_URL = 12;
+ private static final int ROW_AI_PROMPT_HISTORY = 13;
+ private static final int ROW_COUNT = 14;
private static final String[] LOG_PIN_OPTIONS = { "off", "25", "50", "75"
};
private static final String[] RATE_PER_OPTIONS = { "seconds", "minutes" };
@@ -83,6 +84,7 @@ class SettingsPopup {
private int selectTabIndex;
private int logPinIndex;
private int ratePerIndex;
+ private int confirmActionsIndex;
private int aiProviderIndex;
private TextInputState folderInput;
private TextInputState proxyHostInput;
@@ -158,6 +160,8 @@ class SettingsPopup {
String currentRatePer = settings.getRatePer() != null ?
settings.getRatePer() : "seconds";
ratePerIndex = "minutes".equals(currentRatePer) ? 1 : 0;
+ confirmActionsIndex = settings.isConfirmActions() ? 1 : 0;
+
folderInput = new TextInputState(settings.getDefaultFolder() != null ?
settings.getDefaultFolder() : "");
proxyHostInput = new TextInputState(settings.getProxyHost() != null ?
settings.getProxyHost() : "");
proxyPortInput = new TextInputState(settings.getProxyPort() != null ?
settings.getProxyPort() : "");
@@ -253,6 +257,12 @@ class SettingsPopup {
}
return true;
}
+ if (selectedRow == ROW_CONFIRM_ACTIONS) {
+ if (ke.isChar(' ') || ke.isRight() || ke.isLeft()) {
+ confirmActionsIndex = confirmActionsIndex == 0 ? 1 : 0;
+ }
+ return true;
+ }
if (selectedRow == ROW_FOLDER) {
handleTextInput(ke, folderInput);
return true;
@@ -309,6 +319,10 @@ class SettingsPopup {
if (monitorContext != null) {
monitorContext.ratePerMinute = "minutes".equals(ratePerValue);
}
+ settings.setConfirmActions(confirmActionsIndex == 1 ? "true" :
"false");
+ if (monitorContext != null) {
+ monitorContext.confirmActions = confirmActionsIndex == 1;
+ }
settings.setDefaultFolder(stripControlChars(folderInput.text().trim()));
settings.setProxyHost(stripControlChars(proxyHostInput.text().trim()));
settings.setProxyPort(stripControlChars(proxyPortInput.text().trim()));
@@ -373,6 +387,11 @@ class SettingsPopup {
renderValue(frame, innerX + labelW, rowY, fieldW,
RATE_PER_OPTIONS[ratePerIndex], selectedRow == ROW_RATE_PER);
rowY++;
+ renderLabel(frame, innerX, rowY, labelW, "Confirm:", selectedRow ==
ROW_CONFIRM_ACTIONS);
+ renderValue(frame, innerX + labelW, rowY, fieldW, confirmActionsIndex
== 1 ? "on" : "off",
+ selectedRow == ROW_CONFIRM_ACTIONS);
+ rowY++;
+
renderLabel(frame, innerX, rowY, labelW, "Default Folder:",
selectedRow == ROW_FOLDER);
renderFolder(frame, innerX + labelW, rowY, fieldW, selectedRow ==
ROW_FOLDER);
rowY++;
@@ -414,7 +433,7 @@ class SettingsPopup {
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_AI_PROVIDER) {
+ || selectedRow == ROW_CONFIRM_ACTIONS || selectedRow ==
ROW_AI_PROVIDER) {
hint(spans, "Space", "cycle");
}
hint(spans, "Enter", "save");
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiSettings.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiSettings.java
index d586473ce3fc..65ef4e5bc427 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiSettings.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiSettings.java
@@ -43,6 +43,7 @@ final class TuiSettings {
static final String PROP_PROXY_PORT = "camel.tui.proxyPort";
static final String PROP_SHELL_HISTORY = "camel.tui.shell.history";
static final String PROP_AI_PROMPT_HISTORY = "camel.tui.ai.promptHistory";
+ static final String PROP_CONFIRM_ACTIONS = "camel.tui.confirmActions";
private String themeId;
private String startTab;
@@ -57,6 +58,7 @@ final class TuiSettings {
private String aiUrl;
private String shellHistory;
private String aiPromptHistory;
+ private String confirmActions;
String getThemeId() {
return themeId;
@@ -170,6 +172,18 @@ final class TuiSettings {
return TuiHistoryLimits.resolveLimit(aiPromptHistory);
}
+ String getConfirmActions() {
+ return confirmActions;
+ }
+
+ void setConfirmActions(String confirmActions) {
+ this.confirmActions = confirmActions;
+ }
+
+ boolean isConfirmActions() {
+ return !"false".equals(confirmActions);
+ }
+
/**
* Loads the current settings, resolving each key with per-key
local/global precedence via {@link TuiUserConfig}.
* Unset keys yield {@code null} fields; a read failure yields an object
with {@code null} fields rather than
@@ -191,6 +205,7 @@ final class TuiSettings {
settings.aiUrl = trimToNull(TuiUserConfig.read(PROP_AI_URL));
settings.shellHistory =
trimToNull(TuiUserConfig.read(PROP_SHELL_HISTORY));
settings.aiPromptHistory =
trimToNull(TuiUserConfig.read(PROP_AI_PROMPT_HISTORY));
+ settings.confirmActions =
trimToNull(TuiUserConfig.read(PROP_CONFIRM_ACTIONS));
} catch (RuntimeException e) {
// best-effort: return an object with null fields on read failure
}
@@ -217,6 +232,7 @@ final class TuiSettings {
TuiUserConfig.write(PROP_AI_URL, aiUrl);
TuiUserConfig.write(PROP_SHELL_HISTORY, shellHistory);
TuiUserConfig.write(PROP_AI_PROMPT_HISTORY, aiPromptHistory);
+ TuiUserConfig.write(PROP_CONFIRM_ACTIONS, confirmActions);
} catch (RuntimeException e) {
// best-effort: a save failure must not disrupt the TUI
}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/gruvbox-light.tcss
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/gruvbox-light.tcss
index e8902dead615..8cf77b57ad8e 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/gruvbox-light.tcss
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/gruvbox-light.tcss
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/* Camel TUI Gruvbox Light theme. Warm sepia-toned light palette with earthy
colors. */
+/* Camel TUI Gruvbox Light theme. Retro groove palette on a warm light base. */
$brand: #AF3A03;
$gruvbox-bg: #FBF1C7;
@@ -28,19 +28,19 @@ $gruvbox-fg: #3C3836;
#title { color: $brand; text-style: bold; }
#success { color: #79740E; }
#warning { color: #B57614; }
-#error { color: #CC241D; }
+#error { color: #9D0006; }
#muted { color: #928374; }
-#selection { color: white; background: #D65D0E; text-style: bold; }
+#selection { color: white; background: #AF3A03; text-style: bold; }
#info { color: #076678; }
#notice { color: #8F3F71; }
-#row-alt { background: #F2E5BC; }
+#row-alt { background: #EBDBB2; }
#base-bg { background: $gruvbox-bg; }
#base-fg { color: $gruvbox-fg; }
/* Content tokens — labels, change indicators, search highlight */
#label { color: #B57614; }
#change { color: #B57614; }
-#search-match { color: $gruvbox-fg; background: #B57614; }
+#search-match { color: #3C3836; background: #B57614; }
#mnemonic { color: #B57614; text-style: bold underline; }
/* Diagram tokens */
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SettingsPopupTest.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SettingsPopupTest.java
index f353f54cf057..858ae671b17a 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SettingsPopupTest.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SettingsPopupTest.java
@@ -137,7 +137,8 @@ class SettingsPopupTest {
popup.handleKeyEvent(key(KeyCode.DOWN));
popup.handleKeyEvent(key(KeyCode.DOWN));
popup.handleKeyEvent(key(KeyCode.DOWN));
- assertEquals(5, popup.selectedRow());
+ popup.handleKeyEvent(key(KeyCode.DOWN));
+ assertEquals(6, popup.selectedRow());
popup.handleKeyEvent(KeyEvent.ofChar('/'));
popup.handleKeyEvent(KeyEvent.ofChar('a'));
assertEquals("/a", popup.folderText());
@@ -155,7 +156,8 @@ class SettingsPopupTest {
popup.handleKeyEvent(key(KeyCode.DOWN));
popup.handleKeyEvent(key(KeyCode.DOWN));
popup.handleKeyEvent(key(KeyCode.DOWN));
- assertEquals(5, popup.selectedRow());
+ popup.handleKeyEvent(key(KeyCode.DOWN));
+ assertEquals(6, popup.selectedRow());
popup.handleKeyEvent(KeyEvent.ofChar(0x01));
popup.handleKeyEvent(KeyEvent.ofChar(0x00));
popup.handleKeyEvent(KeyEvent.ofChar('x'));
@@ -179,7 +181,8 @@ class SettingsPopupTest {
popup.handleKeyEvent(key(KeyCode.DOWN));
popup.handleKeyEvent(key(KeyCode.DOWN));
popup.handleKeyEvent(key(KeyCode.DOWN));
- assertEquals(9, popup.selectedRow());
+ popup.handleKeyEvent(key(KeyCode.DOWN));
+ assertEquals(10, popup.selectedRow());
assertEquals("auto", popup.selectedAiProvider());
popup.handleKeyEvent(KeyEvent.ofChar(' '));
assertEquals("ollama", popup.selectedAiProvider());
@@ -211,21 +214,21 @@ class SettingsPopupTest {
popup.setTabEntries(tabs());
popup.open();
- // navigate to Shell History (row 8)
- for (int i = 0; i < 8; i++) {
+ // navigate to Shell History (row 9)
+ for (int i = 0; i < 9; i++) {
popup.handleKeyEvent(key(KeyCode.DOWN));
}
- assertEquals(8, popup.selectedRow());
+ assertEquals(9, popup.selectedRow());
for (char c : "50".toCharArray()) {
popup.handleKeyEvent(KeyEvent.ofChar(c));
}
assertEquals("50", popup.shellHistoryText());
- // navigate to AI Prompt History (row 12)
+ // navigate to AI Prompt History (row 13)
for (int i = 0; i < 4; i++) {
popup.handleKeyEvent(key(KeyCode.DOWN));
}
- assertEquals(12, popup.selectedRow());
+ assertEquals(13, popup.selectedRow());
for (char c : "200".toCharArray()) {
popup.handleKeyEvent(KeyEvent.ofChar(c));
}
@@ -258,6 +261,7 @@ class SettingsPopupTest {
popup.handleKeyEvent(key(KeyCode.DOWN)); // select tab
popup.handleKeyEvent(key(KeyCode.DOWN)); // log pin
popup.handleKeyEvent(key(KeyCode.DOWN)); // rate per
+ popup.handleKeyEvent(key(KeyCode.DOWN)); // confirm actions
popup.handleKeyEvent(key(KeyCode.DOWN)); // folder
for (char c : "/tmp/p".toCharArray()) {
popup.handleKeyEvent(KeyEvent.ofChar(c));