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 9e4d264f6058 camel-jbang - TUI expand theme system to 13 named themes
with live-preview picker
9e4d264f6058 is described below
commit 9e4d264f6058df95f4984ef1e221d2549ce0e272
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Jul 9 07:17:41 2026 +0200
camel-jbang - TUI expand theme system to 13 named themes with live-preview
picker
camel-jbang - TUI use native accent colors per theme instead of Camel orange
camel-jbang - TUI add Monochrome and CRT themes, tone down light theme
accent
camel-jbang - TUI fix hint-key contrast on themes with dark brand colors
camel-jbang - TUI add MCP tools tui_get_themes and tui_set_theme
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
---
.../dsl/jbang/core/commands/tui/ActionsPopup.java | 58 +++--
.../dsl/jbang/core/commands/tui/CamelMonitor.java | 7 +-
.../camel/dsl/jbang/core/commands/tui/Theme.java | 46 +++-
.../dsl/jbang/core/commands/tui/ThemeMode.java | 54 +++-
.../dsl/jbang/core/commands/tui/ThemePopup.java | 276 +++++++++++++++++++++
.../dsl/jbang/core/commands/tui/TuiIcons.java | 1 +
.../dsl/jbang/core/commands/tui/TuiMcpServer.java | 49 ++++
.../resources/tui/themes/catppuccin-latte.tcss | 54 ++++
.../themes/{light.tcss => catppuccin-mocha.tcss} | 54 ++--
.../src/main/resources/tui/themes/crt.tcss | 54 ++++
.../tui/themes/{light.tcss => dracula.tcss} | 54 ++--
.../tui/themes/{light.tcss => everforest.tcss} | 54 ++--
.../tui/themes/{light.tcss => gruvbox-dark.tcss} | 54 ++--
.../src/main/resources/tui/themes/kanagawa.tcss | 54 ++++
.../src/main/resources/tui/themes/light.tcss | 4 +-
.../src/main/resources/tui/themes/monochrome.tcss | 54 ++++
.../resources/tui/themes/{light.tcss => nord.tcss} | 54 ++--
.../tui/themes/{light.tcss => rose-pine.tcss} | 54 ++--
.../tui/themes/{light.tcss => solarized-dark.tcss} | 54 ++--
.../main/resources/tui/themes/solarized-light.tcss | 54 ++++
.../src/main/resources/tui/themes/tokyo-night.tcss | 54 ++++
.../tui/ThemeModeCompletionCandidatesTest.java | 7 +-
.../dsl/jbang/core/commands/tui/ThemeModeTest.java | 31 ++-
.../dsl/jbang/core/commands/tui/ThemeTest.java | 69 +++++-
24 files changed, 1066 insertions(+), 238 deletions(-)
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
index acd322e03f8b..75be54bd7bf2 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
@@ -66,6 +66,7 @@ class ActionsPopup {
BACK,
SCREENSHOT,
TOGGLE_THEME,
+ THEMES_SUBMENU,
RESET_SCREEN,
TAPE_RECORDING,
TAPE_INSTRUCTIONS,
@@ -87,7 +88,7 @@ class ActionsPopup {
private final Runnable burstCallback;
private Runnable resetStatsAction;
private Runnable resetScreenAction;
- private Runnable themeToggleAction;
+ private Runnable themeRefreshAction;
private Runnable openShellAction;
private Runnable browseFilesAction;
private Runnable switchIntegrationAction;
@@ -106,6 +107,7 @@ class ActionsPopup {
private Rect actionsMenuRect;
private final GotoTabPopup gotoTabPopup = new GotoTabPopup();
+ private final ThemePopup themePopup = new ThemePopup();
private final DocViewerPopup docViewerPopup = new DocViewerPopup();
private final ExampleBrowserPopup exampleBrowserPopup;
@@ -177,8 +179,9 @@ class ActionsPopup {
this.resetScreenAction = resetScreenAction;
}
- void setThemeToggleAction(Runnable themeToggleAction) {
- this.themeToggleAction = themeToggleAction;
+ void setThemeRefreshAction(Runnable themeRefreshAction) {
+ this.themeRefreshAction = themeRefreshAction;
+ themePopup.setThemeRefreshAction(themeRefreshAction);
}
void setOpenShellAction(Runnable openShellAction) {
@@ -264,7 +267,7 @@ class ActionsPopup {
List<Action> flat = new ArrayList<>();
flat.add(Action.BACK);
flat.add(null);
- flat.addAll(List.of(Action.SCREENSHOT, Action.TOGGLE_THEME,
Action.RESET_SCREEN));
+ flat.addAll(List.of(Action.SCREENSHOT, Action.THEMES_SUBMENU,
Action.RESET_SCREEN));
flat.add(null);
flat.addAll(List.of(Action.TAPE_RECORDING, Action.TAPE_INSTRUCTIONS,
Action.CAPTION, Action.SHOW_KEYSTROKES));
return flat;
@@ -312,7 +315,8 @@ class ActionsPopup {
}
boolean isVisible() {
- return showActionsMenu || gotoTabPopup.isVisible() ||
exampleBrowserPopup.isVisible()
+ return showActionsMenu || themePopup.isVisible() ||
gotoTabPopup.isVisible()
+ || exampleBrowserPopup.isVisible()
|| folderInputPopup.isVisible()
|| runOptionsForm.isVisible()
|| docViewerPopup.isVisible()
@@ -322,6 +326,9 @@ class ActionsPopup {
}
SelectionContext getSelectionContext() {
+ if (themePopup.isVisible()) {
+ return themePopup.getSelectionContext();
+ }
if (gotoTabPopup.isVisible()) {
return gotoTabPopup.getSelectionContext();
}
@@ -353,7 +360,7 @@ class ActionsPopup {
labels.add("..");
labels.add("───");
labels.add("Take Screenshot");
- labels.add(Theme.isDark() ? "Light Theme" : "Dark Theme");
+ labels.add("Themes...");
labels.add("Reset Screen");
labels.add("───");
labels.add(tapeRecordingActive.get() ? "Stop Tape Recording" :
"Start Tape Recording");
@@ -403,6 +410,7 @@ class ActionsPopup {
void close() {
showActionsMenu = false;
currentSubmenu = null;
+ themePopup.close();
gotoTabPopup.close();
exampleBrowserPopup.close();
folderInputPopup.close();
@@ -507,6 +515,9 @@ class ActionsPopup {
if (doctorPopup.handleKeyEvent(ke)) {
return true;
}
+ if (themePopup.handleKeyEvent(ke)) {
+ return true;
+ }
if (gotoTabPopup.isVisible()) {
boolean wasVisible = gotoTabPopup.isVisible();
gotoTabPopup.handleKeyEvent(ke);
@@ -551,6 +562,9 @@ class ActionsPopup {
} else if (action == Action.BACK) {
currentSubmenu = null;
actionsMenuState.select(savedMainSelection);
+ } else if (action == Action.THEMES_SUBMENU) {
+ showActionsMenu = false;
+ themePopup.open();
} else if (action == Action.SCREEN_SUBMENU) {
savedMainSelection = sel;
currentSubmenu = "screen";
@@ -633,8 +647,9 @@ class ActionsPopup {
resetScreenAction.run();
}
} else if (action == Action.TOGGLE_THEME) {
+ Theme.toggle();
+ refreshTheme();
showActionsMenu = false;
- toggleTheme();
} else if (action == Action.STOP_ALL) {
showActionsMenu = false;
stopAllPopup.open();
@@ -663,6 +678,9 @@ class ActionsPopup {
if (!isVisible()) {
return false;
}
+ if (themePopup.isVisible()) {
+ return themePopup.handleMouseEvent(me);
+ }
if (gotoTabPopup.isVisible()) {
return gotoTabPopup.handleMouseEvent(me);
}
@@ -726,6 +744,9 @@ class ActionsPopup {
}
void render(Frame frame, Rect area) {
+ if (themePopup.isVisible()) {
+ themePopup.render(frame, area);
+ }
if (gotoTabPopup.isVisible()) {
gotoTabPopup.render(frame, area);
}
@@ -812,6 +833,10 @@ class ActionsPopup {
exampleBrowserPopup.renderFooter(spans);
return;
}
+ if (themePopup.isVisible()) {
+ themePopup.renderFooter(spans);
+ return;
+ }
if (gotoTabPopup.isVisible()) {
hint(spans, "type", "filter");
hint(spans, TuiIcons.HINT_SCROLL, "navigate");
@@ -920,15 +945,11 @@ class ActionsPopup {
String tapeLabel = tapeRecordingActive.get()
? TuiIcons.menuItem(TuiIcons.STOP, "Stop Tape Recording
(Ctrl+R)")
: TuiIcons.menuItem(TuiIcons.RECORD, "Start Tape Recording
(Ctrl+R)");
- String themeLabel = Theme.isDark()
- ? TuiIcons.menuItem(TuiIcons.LIGHT_THEME, "Light Theme")
- : TuiIcons.menuItem(TuiIcons.DARK_THEME, "Dark Theme");
-
List<ListItem> items = new ArrayList<>();
items.add(ListItem.from(" .."));
items.add(ListItem.from(divider).style(Style.EMPTY.dim()));
items.add(ListItem.from(TuiIcons.menuItem(TuiIcons.SCREENSHOT, "Take
Screenshot")));
- items.add(ListItem.from(themeLabel));
+ items.add(ListItem.from(TuiIcons.menuItem(TuiIcons.THEMES,
"Themes...")));
items.add(ListItem.from(TuiIcons.menuItem(TuiIcons.CLEAN, "Reset
Screen")));
items.add(ListItem.from(divider).style(Style.EMPTY.dim()));
items.add(ListItem.from(tapeLabel));
@@ -1185,7 +1206,10 @@ class ActionsPopup {
resetScreenAction.run();
}
}
- case TOGGLE_THEME -> toggleTheme();
+ case TOGGLE_THEME -> {
+ Theme.toggle();
+ refreshTheme();
+ }
case SCREENSHOT -> screenshotAction.run();
case SHOW_KEYSTROKES -> toggleKeystrokes.run();
case TAPE_RECORDING -> toggleTapeRecording.run();
@@ -1206,11 +1230,9 @@ class ActionsPopup {
return gotoTabPopup.consumePendingEntry();
}
- private void toggleTheme() {
- if (themeToggleAction != null) {
- themeToggleAction.run();
- } else {
- Theme.toggle();
+ private void refreshTheme() {
+ if (themeRefreshAction != null) {
+ themeRefreshAction.run();
}
}
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 37114e80abfb..0339c91c4361 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
@@ -110,7 +110,7 @@ public class CamelMonitor extends CamelCommand {
int mcpPort = 8123;
@CommandLine.Option(names = { "--theme" },
- description = "Color theme: dark or light (overrides
persisted preference for this session)",
+ description = "Color theme (overrides persisted
preference for this session)",
completionCandidates =
ThemeModeCompletionCandidates.class)
String theme;
@@ -463,10 +463,7 @@ public class CamelMonitor extends CamelCommand {
ctx.runner = tui;
actionsPopup.setScheduler(tui.scheduler());
actionsPopup.setResetScreenAction(() -> tui.terminal().clear());
- actionsPopup.setThemeToggleAction(() -> {
- Theme.toggle();
- tui.terminal().clear();
- });
+ actionsPopup.setThemeRefreshAction(() -> tui.terminal().clear());
// Preload diagram data if an integration was auto-selected
tabRegistry.routesTab().preloadDiagram();
tabRegistry.diagramTab().preloadDiagram();
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/Theme.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/Theme.java
index 9d2a2b54b729..b03634569bef 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/Theme.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/Theme.java
@@ -108,6 +108,7 @@ public final class Theme {
private static boolean testMode;
private static StyleEngine engine;
private static ThemeMode mode = ThemeMode.DARK;
+ private static ThemeMode previewOriginal;
private Theme() {
}
@@ -315,9 +316,49 @@ public final class Theme {
return mode == ThemeMode.DARK;
}
- /** Flip the active theme mode, persist it (outside test mode), and
activate it, returning the new mode. */
+ /** Cycle to the next theme in declaration order, persist it (outside test
mode), and activate it. */
public static synchronized String toggle() {
- ThemeMode next = mode.toggle();
+ ThemeMode next = mode.next();
+ if (!testMode) {
+ persist(next);
+ }
+ activate(next, false);
+ return next.id();
+ }
+
+ /** Apply a theme as a live preview without persisting. Saves the original
mode on the first call in a sequence. */
+ public static synchronized void preview(String modeId) {
+ if (previewOriginal == null) {
+ previewOriginal = mode;
+ }
+ activate(ThemeMode.parseOrDefault(modeId), false);
+ }
+
+ /** Restore the theme that was active before previewing started. No-op if
not previewing. */
+ public static synchronized void revertPreview() {
+ if (previewOriginal != null) {
+ ThemeMode original = previewOriginal;
+ previewOriginal = null;
+ activate(original, false);
+ }
+ }
+
+ /** Persist the currently previewed theme and clear the preview state. */
+ public static synchronized void confirmPreview() {
+ previewOriginal = null;
+ if (!testMode) {
+ persist(mode);
+ }
+ }
+
+ /** The persisted theme mode id (unaffected by an in-progress preview). */
+ public static synchronized String persistedMode() {
+ return previewOriginal != null ? previewOriginal.id() : mode.id();
+ }
+
+ /** Apply and persist a specific theme. For programmatic / MCP use. */
+ public static synchronized String setTheme(String modeId) {
+ ThemeMode next = ThemeMode.parseOrDefault(modeId);
if (!testMode) {
persist(next);
}
@@ -370,6 +411,7 @@ public final class Theme {
persistedReadFallbackLogged = false;
persistedWriteFallbackLogged = false;
persistedModeLoaded = false;
+ previewOriginal = null;
CACHE.clear();
engine = null;
initialized = false;
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ThemeMode.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ThemeMode.java
index 812505ae8233..a800053c8de0 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ThemeMode.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ThemeMode.java
@@ -21,31 +21,59 @@ import java.util.List;
import java.util.Locale;
import java.util.Optional;
-/** Canonical dark/light palette selector for the Camel TUI. */
+/** Named color theme selector for the Camel TUI. */
enum ThemeMode {
- DARK("dark"),
- LIGHT("light");
+ DARK("dark", "Dark", false),
+ LIGHT("light", "Light", true),
+ DRACULA("dracula", "Dracula", false),
+ NORD("nord", "Nord", false),
+ SOLARIZED_DARK("solarized-dark", "Solarized Dark", false),
+ SOLARIZED_LIGHT("solarized-light", "Solarized Light", true),
+ GRUVBOX_DARK("gruvbox-dark", "Gruvbox Dark", false),
+ CATPPUCCIN_MOCHA("catppuccin-mocha", "Catppuccin Mocha", false),
+ CATPPUCCIN_LATTE("catppuccin-latte", "Catppuccin Latte", true),
+ TOKYO_NIGHT("tokyo-night", "Tokyo Night", false),
+ ROSE_PINE("rose-pine", "Rosé Pine", false),
+ KANAGAWA("kanagawa", "Kanagawa", false),
+ EVERFOREST("everforest", "Everforest", false),
+ MONOCHROME("monochrome", "Monochrome", false),
+ CRT("crt", "CRT", false);
private final String id;
+ private final String label;
+ private final boolean light;
- ThemeMode(String id) {
+ ThemeMode(String id, String label, boolean light) {
this.id = id;
+ this.label = label;
+ this.light = light;
}
- /** Lowercase config / CLI value ({@code dark} or {@code light}). */
+ /** Lowercase config / CLI value (e.g. {@code dark}, {@code
catppuccin-mocha}). */
String id() {
return id;
}
+ /** Human-readable display name (e.g. {@code Catppuccin Mocha}). */
+ String label() {
+ return label;
+ }
+
+ /** Whether this is a light-background theme. */
+ boolean isLight() {
+ return light;
+ }
+
/** Classpath resource path of this mode's stylesheet, e.g. {@code
tui/themes/dark.tcss}. */
String stylesheetResource() {
return "tui/themes/" + id + ".tcss";
}
- // Only two modes exist today, so toggling is a simple flip; revisit this
if a third mode is ever added.
- ThemeMode toggle() {
- return this == DARK ? LIGHT : DARK;
+ /** Returns the next theme in declaration order, wrapping around at the
end. */
+ ThemeMode next() {
+ ThemeMode[] all = values();
+ return all[(ordinal() + 1) % all.length];
}
static Optional<ThemeMode> parse(String value) {
@@ -68,4 +96,14 @@ enum ThemeMode {
static List<String> ids() {
return Arrays.stream(values()).map(m -> m.id).toList();
}
+
+ /** Dark themes in declaration order. */
+ static List<ThemeMode> darkThemes() {
+ return Arrays.stream(values()).filter(m -> !m.light).toList();
+ }
+
+ /** Light themes in declaration order. */
+ static List<ThemeMode> lightThemes() {
+ return Arrays.stream(values()).filter(m -> m.light).toList();
+ }
}
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
new file mode 100644
index 000000000000..88ac5babaf0e
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ThemePopup.java
@@ -0,0 +1,276 @@
+/*
+ * 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 dev.tamboui.layout.Rect;
+import dev.tamboui.style.Style;
+import dev.tamboui.terminal.Frame;
+import dev.tamboui.text.Span;
+import dev.tamboui.tui.event.KeyCode;
+import dev.tamboui.tui.event.KeyEvent;
+import dev.tamboui.tui.event.MouseEvent;
+import dev.tamboui.tui.event.MouseEventKind;
+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.list.ListItem;
+import dev.tamboui.widgets.list.ListState;
+import dev.tamboui.widgets.list.ListWidget;
+import dev.tamboui.widgets.list.ScrollMode;
+
+import static org.apache.camel.dsl.jbang.core.commands.tui.TuiHelper.hint;
+import static org.apache.camel.dsl.jbang.core.commands.tui.TuiHelper.hintLast;
+
+/**
+ * Self-contained theme picker popup with live preview. Shows dark and light
themes in separate sections; scrolling
+ * applies the theme instantly, Enter confirms and persists, Esc reverts to
the original theme.
+ */
+class ThemePopup {
+
+ private boolean visible;
+ private final ListState listState = new ListState();
+ private Runnable themeRefreshAction;
+ private Rect popupRect;
+
+ void setThemeRefreshAction(Runnable action) {
+ this.themeRefreshAction = action;
+ }
+
+ void open() {
+ visible = true;
+ ThemeMode current = ThemeMode.parseOrDefault(Theme.mode());
+ listState.select(visualIndexForTheme(current));
+ }
+
+ void close() {
+ if (visible) {
+ Theme.revertPreview();
+ refreshTheme();
+ }
+ visible = false;
+ }
+
+ boolean isVisible() {
+ return visible;
+ }
+
+ boolean handleKeyEvent(KeyEvent ke) {
+ if (!visible) {
+ return false;
+ }
+ if (ke.isCancel()) {
+ Theme.revertPreview();
+ refreshTheme();
+ visible = false;
+ } else if (ke.isUp()) {
+ navigate(-1);
+ applyThemePreview();
+ } else if (ke.isDown()) {
+ navigate(1);
+ applyThemePreview();
+ } else if (ke.isHome() || ke.isKey(KeyCode.HOME)) {
+ listState.select(2);
+ applyThemePreview();
+ } else if (ke.isEnd() || ke.isKey(KeyCode.END)) {
+ listState.select(itemCount() - 1);
+ applyThemePreview();
+ } else if (ke.isConfirm()) {
+ Integer sel = listState.selected();
+ if (sel != null && sel == 0) {
+ Theme.revertPreview();
+ refreshTheme();
+ visible = false;
+ } else {
+ ThemeMode mode = themeAtVisualIndex(sel);
+ if (mode != null) {
+ Theme.confirmPreview();
+ refreshTheme();
+ visible = false;
+ }
+ }
+ }
+ return true;
+ }
+
+ boolean handleMouseEvent(MouseEvent me) {
+ if (!visible) {
+ return false;
+ }
+ if (me.kind() == MouseEventKind.SCROLL_UP) {
+ handleKeyEvent(KeyEvent.ofKey(KeyCode.UP));
+ return true;
+ }
+ if (me.kind() == MouseEventKind.SCROLL_DOWN) {
+ handleKeyEvent(KeyEvent.ofKey(KeyCode.DOWN));
+ return true;
+ }
+ if (me.isClick()) {
+ if (popupRect != null && popupRect.contains(me.x(), me.y())) {
+ int idx = TuiHelper.listItemAt(popupRect, listState.offset(),
itemCount(), me.x(), me.y());
+ if (idx >= 0 && !isDivider(idx)) {
+ listState.select(idx);
+ handleKeyEvent(KeyEvent.ofKey(KeyCode.ENTER));
+ }
+ return true;
+ }
+ handleKeyEvent(KeyEvent.ofKey(KeyCode.ESCAPE));
+ return true;
+ }
+ return true;
+ }
+
+ void render(Frame frame, Rect area) {
+ if (!visible) {
+ return;
+ }
+ int count = itemCount();
+ int popupW = 40;
+ int popupH = 2 + count;
+ int x = area.left() + Math.max(0, (area.width() - popupW) / 2);
+ int y = area.top() + 2;
+ Rect popup = new Rect(x, y, Math.min(popupW, area.width()),
Math.min(popupH, area.height() - 2));
+ this.popupRect = popup;
+
+ frame.renderWidget(Clear.INSTANCE, popup);
+ String persisted = Theme.persistedMode();
+
+ List<ListItem> items = new ArrayList<>();
+ items.add(ListItem.from(" .."));
+ items.add(ListItem.from(" ──── Dark
────────────────────").style(Style.EMPTY.dim()));
+ for (ThemeMode m : ThemeMode.darkThemes()) {
+ String check = m.id().equals(persisted) ? " ✔" : "";
+ items.add(ListItem.from(" " + m.label() + check));
+ }
+ items.add(ListItem.from(" ──── Light
───────────────────").style(Style.EMPTY.dim()));
+ for (ThemeMode m : ThemeMode.lightThemes()) {
+ String check = m.id().equals(persisted) ? " ✔" : "";
+ items.add(ListItem.from(" " + m.label() + check));
+ }
+ ListWidget list = ListWidget.builder()
+ .items(items.toArray(ListItem[]::new))
+ .highlightStyle(Theme.selectionBg())
+ .highlightSymbol("")
+ .scrollMode(ScrollMode.NONE)
+ .block(Block.builder()
+ .borderType(BorderType.ROUNDED).borders(Borders.ALL)
+ .title(" Themes ")
+ .build())
+ .build();
+ frame.renderStatefulWidget(list, popup, listState);
+ }
+
+ void renderFooter(List<Span> spans) {
+ hint(spans, TuiIcons.HINT_SCROLL, "preview");
+ hint(spans, "Enter", "apply");
+ hintLast(spans, "Esc", "back");
+ }
+
+ List<String> getLabels() {
+ List<String> labels = new ArrayList<>();
+ labels.add("..");
+ labels.add("── Dark ──");
+ for (ThemeMode m : ThemeMode.darkThemes()) {
+ labels.add(m.label());
+ }
+ labels.add("── Light ──");
+ for (ThemeMode m : ThemeMode.lightThemes()) {
+ labels.add(m.label());
+ }
+ return labels;
+ }
+
+ SelectionContext getSelectionContext() {
+ List<String> items = getLabels();
+ Integer sel = listState.selected();
+ return new SelectionContext("popup", items, sel != null ? sel : -1,
itemCount(), "Themes");
+ }
+
+ // ---- Private helpers ----
+
+ private int itemCount() {
+ // BACK + dark-divider + darkThemes + light-divider + lightThemes
+ return 2 + ThemeMode.darkThemes().size() + 1 +
ThemeMode.lightThemes().size();
+ }
+
+ private boolean isDivider(int index) {
+ int darkCount = ThemeMode.darkThemes().size();
+ return index == 1 || index == 2 + darkCount;
+ }
+
+ private void navigate(int direction) {
+ int total = itemCount();
+ Integer current = listState.selected();
+ int next = (current != null ? current : 0) + direction;
+ next = Math.max(0, Math.min(next, total - 1));
+ while (isDivider(next) && next > 0 && next < total - 1) {
+ next += direction;
+ }
+ next = Math.max(0, Math.min(next, total - 1));
+ if (!isDivider(next)) {
+ listState.select(next);
+ }
+ }
+
+ private void applyThemePreview() {
+ ThemeMode mode = themeAtVisualIndex(listState.selected());
+ if (mode != null) {
+ Theme.preview(mode.id());
+ refreshTheme();
+ }
+ }
+
+ private ThemeMode themeAtVisualIndex(Integer sel) {
+ if (sel == null) {
+ return null;
+ }
+ List<ThemeMode> dark = ThemeMode.darkThemes();
+ List<ThemeMode> light = ThemeMode.lightThemes();
+ int darkCount = dark.size();
+ if (sel >= 2 && sel < 2 + darkCount) {
+ return dark.get(sel - 2);
+ }
+ int lightStart = 2 + darkCount + 1;
+ if (sel >= lightStart && sel < lightStart + light.size()) {
+ return light.get(sel - lightStart);
+ }
+ return null;
+ }
+
+ private int visualIndexForTheme(ThemeMode target) {
+ List<ThemeMode> dark = ThemeMode.darkThemes();
+ int darkIdx = dark.indexOf(target);
+ if (darkIdx >= 0) {
+ return 2 + darkIdx;
+ }
+ List<ThemeMode> light = ThemeMode.lightThemes();
+ int lightIdx = light.indexOf(target);
+ if (lightIdx >= 0) {
+ return 2 + dark.size() + 1 + lightIdx;
+ }
+ return 2;
+ }
+
+ private void refreshTheme() {
+ if (themeRefreshAction != null) {
+ themeRefreshAction.run();
+ }
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiIcons.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiIcons.java
index 3a21a76dd939..6c89c4ce9f46 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiIcons.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiIcons.java
@@ -62,6 +62,7 @@ final class TuiIcons {
static final String CLEAN = "🧹";
static final String LIGHT_THEME = "🌞";
static final String DARK_THEME = "🌙";
+ static final String THEMES = "🎨";
static final String SCREENSHOT = "📸";
static final String CAPTION = "💬";
static final String MCP_BRAIN = "🧠";
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiMcpServer.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiMcpServer.java
index f8805af0363d..a286a3e63b38 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiMcpServer.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiMcpServer.java
@@ -402,6 +402,16 @@ class TuiMcpServer {
+ "tape-recording, doctor, caption, mcp-info,
mcp-log, toggle-theme.",
Map.of("action", propDef("string", "Action name in kebab-case
(e.g. 'reset-stats', 'screenshot')")),
List.of("action")));
+ toolList.add(toolDef(
+ "tui_get_themes",
+ "Returns available TUI themes grouped by dark and light, "
+ + "with the currently active theme marked.",
+ Map.of()));
+ toolList.add(toolDef(
+ "tui_set_theme",
+ "Switches the TUI to a named theme. Use tui_get_themes to list
available theme IDs.",
+ Map.of("theme", propDef("string", "Theme ID (e.g. 'dracula',
'nord', 'catppuccin-mocha')")),
+ List.of("theme")));
toolList.add(toolDef(
"tui_get_log",
"Returns recent log lines as structured data with optional
filtering. "
@@ -607,6 +617,8 @@ class TuiMcpServer {
case "tui_draw_clear" -> callDrawClear();
case "tui_get_table" -> callGetTable(args);
case "tui_action" -> callAction(args);
+ case "tui_get_themes" -> callGetThemes();
+ case "tui_set_theme" -> callSetTheme(args);
case "tui_get_log" -> callGetLog(args);
case "tui_get_errors" -> callGetErrors();
case "tui_get_diagram" -> callGetDiagram();
@@ -1170,6 +1182,43 @@ class TuiMcpServer {
+ "tape-recording, doctor, caption, mcp-info, mcp-log,
toggle-theme";
}
+ private String callGetThemes() {
+ JsonObject result = new JsonObject();
+ result.put("current", Theme.mode());
+ JsonArray dark = new JsonArray();
+ for (ThemeMode m : ThemeMode.darkThemes()) {
+ JsonObject t = new JsonObject();
+ t.put("id", m.id());
+ t.put("label", m.label());
+ t.put("active", m.id().equals(Theme.mode()));
+ dark.add(t);
+ }
+ JsonArray light = new JsonArray();
+ for (ThemeMode m : ThemeMode.lightThemes()) {
+ JsonObject t = new JsonObject();
+ t.put("id", m.id());
+ t.put("label", m.label());
+ t.put("active", m.id().equals(Theme.mode()));
+ light.add(t);
+ }
+ result.put("dark", dark);
+ result.put("light", light);
+ return Jsoner.serialize(result);
+ }
+
+ private String callSetTheme(Map<String, Object> args) {
+ String themeId = (String) args.get("theme");
+ if (themeId == null || themeId.isBlank()) {
+ return "Error: theme is required. Use tui_get_themes to list
available IDs.";
+ }
+ if (!Theme.isValidMode(themeId)) {
+ return "Error: unknown theme '" + themeId + "'. Use tui_get_themes
to list available IDs.";
+ }
+ Theme.setTheme(themeId);
+ facade.executeAction("reset-screen");
+ return "Theme switched to '" + themeId + "'";
+ }
+
private String callGetLog(Map<String, Object> args) {
int limit = 50;
if (args.get("limit") instanceof Number n) {
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/catppuccin-latte.tcss
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/catppuccin-latte.tcss
new file mode 100644
index 000000000000..295d336a3db3
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/catppuccin-latte.tcss
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+/* Camel TUI Catppuccin Latte theme. Soothing pastel light palette. */
+
+$brand: #8839EF;
+$latte-bg: #EFF1F5;
+$latte-fg: #4C4F69;
+
+#accent { color: $brand; }
+#accent-bg { color: white; background: $brand; text-style: bold; }
+#hint-key { color: white; background: #7C3AED; text-style: bold; }
+#border { color: #BCC0CC; }
+#border-focused { color: $brand; }
+#title { color: $brand; text-style: bold; }
+#success { color: #40A02B; }
+#warning { color: #DF8E1D; }
+#error { color: #D20F39; }
+#muted { color: #9CA0B0; }
+#selection { color: white; background: #1E66F5; text-style: bold; }
+#info { color: #1E66F5; }
+#notice { color: #8839EF; }
+#row-alt { background: #E6E9EF; }
+#base-bg { background: $latte-bg; }
+#base-fg { color: $latte-fg; }
+
+/* Content tokens — labels, change indicators, search highlight */
+#label { color: #DF8E1D; }
+#change { color: #DF8E1D; }
+#search-match { color: #4C4F69; background: #DF8E1D; }
+#mnemonic { color: #DF8E1D; text-style: bold underline; }
+
+/* Diagram tokens */
+#diagram-border { color: #8C8FA1; }
+#diagram-id { color: $latte-fg; }
+#diagram-from { color: #40A02B; }
+#diagram-to { color: #179299; }
+#diagram-choice { color: #DF8E1D; }
+#diagram-action { color: #8839EF; }
+#diagram-eip { color: #7287FD; }
+#diagram-default { color: #9CA0B0; }
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/light.tcss
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/catppuccin-mocha.tcss
similarity index 51%
copy from
dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/light.tcss
copy to
dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/catppuccin-mocha.tcss
index a6a9a285a348..e704f5f4828f 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/light.tcss
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/catppuccin-mocha.tcss
@@ -14,41 +14,41 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/* Camel TUI light theme. Brand orange stays truecolor; status hues darkened
for light terminals. */
+/* Camel TUI Catppuccin Mocha theme. Soothing pastel dark palette. */
-$brand: #F69123;
-$light-bg: #FFFFFF;
-$light-fg: #24292E;
+$brand: #CBA6F7;
+$mocha-bg: #1E1E2E;
+$mocha-fg: #CDD6F4;
#accent { color: $brand; }
#accent-bg { color: white; background: $brand; text-style: bold; }
#hint-key { color: black; background: $brand; text-style: bold; }
-#border { color: #C0C0C0; }
+#border { color: #45475A; }
#border-focused { color: $brand; }
#title { color: $brand; text-style: bold; }
-#success { color: #22863A; }
-#warning { color: #B08800; }
-#error { color: #D73A49; }
-#muted { color: #6A737D; }
-#selection { color: white; background: #0366D6; text-style: bold; }
-#info { color: #0366D6; }
-#notice { color: #6F42C1; }
-#row-alt { background: #F6F8FA; }
-#base-bg { background: $light-bg; }
-#base-fg { color: $light-fg; }
+#success { color: #A6E3A1; }
+#warning { color: #F9E2AF; }
+#error { color: #F38BA8; }
+#muted { color: #6C7086; }
+#selection { color: white; background: #45475A; text-style: bold; }
+#info { color: #89B4FA; }
+#notice { color: #CBA6F7; }
+#row-alt { background: #252536; }
+#base-bg { background: $mocha-bg; }
+#base-fg { color: $mocha-fg; }
/* Content tokens — labels, change indicators, search highlight */
-#label { color: #735C00; }
-#change { color: #B08800; }
-#search-match { color: black; background: #FFD33D; }
-#mnemonic { color: #735C00; text-style: bold underline; }
+#label { color: #F9E2AF; }
+#change { color: #F9E2AF; }
+#search-match { color: black; background: #F9E2AF; }
+#mnemonic { color: #F9E2AF; text-style: bold underline; }
/* Diagram tokens */
-#diagram-border { color: #808080; }
-#diagram-id { color: $light-fg; }
-#diagram-from { color: #22863A; }
-#diagram-to { color: #0366D6; }
-#diagram-choice { color: #B08800; }
-#diagram-action { color: #6F42C1; }
-#diagram-eip { color: #5A32A3; }
-#diagram-default { color: #6A737D; }
+#diagram-border { color: #6C7086; }
+#diagram-id { color: $mocha-fg; }
+#diagram-from { color: #A6E3A1; }
+#diagram-to { color: #94E2D5; }
+#diagram-choice { color: #F9E2AF; }
+#diagram-action { color: #CBA6F7; }
+#diagram-eip { color: #B4BEFE; }
+#diagram-default { color: #6C7086; }
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/crt.tcss
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/crt.tcss
new file mode 100644
index 000000000000..b391f99c4a8f
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/crt.tcss
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+/* Camel TUI CRT theme — retro green terminal. */
+
+$brand: #33FF33;
+$dark-bg: #0A0A0A;
+$dark-fg: #33CC33;
+
+#accent { color: $brand; }
+#accent-bg { color: black; background: $brand; text-style: bold; }
+#hint-key { color: black; background: #33CC33; text-style: bold; }
+#border { color: #1A661A; }
+#border-focused { color: $brand; }
+#title { color: $brand; text-style: bold; }
+#success { color: #33FF33; }
+#warning { color: #66FF66; text-style: bold; }
+#error { color: #BBFFBB; text-style: bold; }
+#muted { color: #1A661A; }
+#selection { color: black; background: #33CC33; text-style: bold; }
+#info { color: #44DD44; }
+#notice { color: #22AA22; }
+#row-alt { background: #0F130F; }
+#base-bg { background: $dark-bg; }
+#base-fg { color: $dark-fg; }
+
+/* Content tokens */
+#label { color: #66FF66; }
+#change { color: #66FF66; }
+#search-match { color: black; background: #66FF66; }
+#mnemonic { color: #66FF66; text-style: bold underline; }
+
+/* Diagram tokens */
+#diagram-border { color: #1A661A; }
+#diagram-id { color: $dark-fg; }
+#diagram-from { color: #44DD44; }
+#diagram-to { color: #22AA22; }
+#diagram-choice { color: #66FF66; }
+#diagram-action { color: #119911; }
+#diagram-eip { color: #33CC33; }
+#diagram-default { color: #1A661A; }
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/light.tcss
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/dracula.tcss
similarity index 53%
copy from
dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/light.tcss
copy to
dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/dracula.tcss
index a6a9a285a348..d7e616fd87c5 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/light.tcss
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/dracula.tcss
@@ -14,41 +14,41 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/* Camel TUI light theme. Brand orange stays truecolor; status hues darkened
for light terminals. */
+/* Camel TUI Dracula theme. Dark theme based on the Dracula color palette. */
-$brand: #F69123;
-$light-bg: #FFFFFF;
-$light-fg: #24292E;
+$brand: #BD93F9;
+$dracula-bg: #282A36;
+$dracula-fg: #F8F8F2;
#accent { color: $brand; }
#accent-bg { color: white; background: $brand; text-style: bold; }
#hint-key { color: black; background: $brand; text-style: bold; }
-#border { color: #C0C0C0; }
+#border { color: #6272A4; }
#border-focused { color: $brand; }
#title { color: $brand; text-style: bold; }
-#success { color: #22863A; }
-#warning { color: #B08800; }
-#error { color: #D73A49; }
-#muted { color: #6A737D; }
-#selection { color: white; background: #0366D6; text-style: bold; }
-#info { color: #0366D6; }
-#notice { color: #6F42C1; }
-#row-alt { background: #F6F8FA; }
-#base-bg { background: $light-bg; }
-#base-fg { color: $light-fg; }
+#success { color: #50FA7B; }
+#warning { color: #F1FA8C; }
+#error { color: #FF5555; }
+#muted { color: #6272A4; }
+#selection { color: white; background: #44475A; text-style: bold; }
+#info { color: #8BE9FD; }
+#notice { color: #BD93F9; }
+#row-alt { background: #2D2F3B; }
+#base-bg { background: $dracula-bg; }
+#base-fg { color: $dracula-fg; }
/* Content tokens — labels, change indicators, search highlight */
-#label { color: #735C00; }
-#change { color: #B08800; }
-#search-match { color: black; background: #FFD33D; }
-#mnemonic { color: #735C00; text-style: bold underline; }
+#label { color: #F1FA8C; }
+#change { color: #F1FA8C; }
+#search-match { color: black; background: #F1FA8C; }
+#mnemonic { color: #F1FA8C; text-style: bold underline; }
/* Diagram tokens */
-#diagram-border { color: #808080; }
-#diagram-id { color: $light-fg; }
-#diagram-from { color: #22863A; }
-#diagram-to { color: #0366D6; }
-#diagram-choice { color: #B08800; }
-#diagram-action { color: #6F42C1; }
-#diagram-eip { color: #5A32A3; }
-#diagram-default { color: #6A737D; }
+#diagram-border { color: #6272A4; }
+#diagram-id { color: $dracula-fg; }
+#diagram-from { color: #50FA7B; }
+#diagram-to { color: #8BE9FD; }
+#diagram-choice { color: #F1FA8C; }
+#diagram-action { color: #FF79C6; }
+#diagram-eip { color: #BD93F9; }
+#diagram-default { color: #6272A4; }
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/light.tcss
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/everforest.tcss
similarity index 51%
copy from
dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/light.tcss
copy to
dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/everforest.tcss
index a6a9a285a348..72fe0546c831 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/light.tcss
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/everforest.tcss
@@ -14,41 +14,41 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/* Camel TUI light theme. Brand orange stays truecolor; status hues darkened
for light terminals. */
+/* Camel TUI Everforest theme. Comfortable green-toned dark palette inspired
by natural forests. */
-$brand: #F69123;
-$light-bg: #FFFFFF;
-$light-fg: #24292E;
+$brand: #A7C080;
+$everforest-bg: #2D353B;
+$everforest-fg: #D3C6AA;
#accent { color: $brand; }
#accent-bg { color: white; background: $brand; text-style: bold; }
#hint-key { color: black; background: $brand; text-style: bold; }
-#border { color: #C0C0C0; }
+#border { color: #7A8478; }
#border-focused { color: $brand; }
#title { color: $brand; text-style: bold; }
-#success { color: #22863A; }
-#warning { color: #B08800; }
-#error { color: #D73A49; }
-#muted { color: #6A737D; }
-#selection { color: white; background: #0366D6; text-style: bold; }
-#info { color: #0366D6; }
-#notice { color: #6F42C1; }
-#row-alt { background: #F6F8FA; }
-#base-bg { background: $light-bg; }
-#base-fg { color: $light-fg; }
+#success { color: #A7C080; }
+#warning { color: #DBBC7F; }
+#error { color: #E67E80; }
+#muted { color: #7A8478; }
+#selection { color: white; background: #343F44; text-style: bold; }
+#info { color: #7FBBB3; }
+#notice { color: #D699B6; }
+#row-alt { background: #343F44; }
+#base-bg { background: $everforest-bg; }
+#base-fg { color: $everforest-fg; }
/* Content tokens — labels, change indicators, search highlight */
-#label { color: #735C00; }
-#change { color: #B08800; }
-#search-match { color: black; background: #FFD33D; }
-#mnemonic { color: #735C00; text-style: bold underline; }
+#label { color: #DBBC7F; }
+#change { color: #DBBC7F; }
+#search-match { color: black; background: #DBBC7F; }
+#mnemonic { color: #DBBC7F; text-style: bold underline; }
/* Diagram tokens */
-#diagram-border { color: #808080; }
-#diagram-id { color: $light-fg; }
-#diagram-from { color: #22863A; }
-#diagram-to { color: #0366D6; }
-#diagram-choice { color: #B08800; }
-#diagram-action { color: #6F42C1; }
-#diagram-eip { color: #5A32A3; }
-#diagram-default { color: #6A737D; }
+#diagram-border { color: #7A8478; }
+#diagram-id { color: $everforest-fg; }
+#diagram-from { color: #A7C080; }
+#diagram-to { color: #7FBBB3; }
+#diagram-choice { color: #DBBC7F; }
+#diagram-action { color: #E69875; }
+#diagram-eip { color: #D699B6; }
+#diagram-default { color: #7A8478; }
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/light.tcss
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/gruvbox-dark.tcss
similarity index 51%
copy from
dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/light.tcss
copy to
dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/gruvbox-dark.tcss
index a6a9a285a348..ef54c4a452ca 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/light.tcss
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/gruvbox-dark.tcss
@@ -14,41 +14,41 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/* Camel TUI light theme. Brand orange stays truecolor; status hues darkened
for light terminals. */
+/* Camel TUI Gruvbox Dark theme. Retro groove palette with warm earthy tones.
*/
-$brand: #F69123;
-$light-bg: #FFFFFF;
-$light-fg: #24292E;
+$brand: #FE8019;
+$gruvbox-bg: #282828;
+$gruvbox-fg: #EBDBB2;
#accent { color: $brand; }
#accent-bg { color: white; background: $brand; text-style: bold; }
#hint-key { color: black; background: $brand; text-style: bold; }
-#border { color: #C0C0C0; }
+#border { color: #504945; }
#border-focused { color: $brand; }
#title { color: $brand; text-style: bold; }
-#success { color: #22863A; }
-#warning { color: #B08800; }
-#error { color: #D73A49; }
-#muted { color: #6A737D; }
-#selection { color: white; background: #0366D6; text-style: bold; }
-#info { color: #0366D6; }
-#notice { color: #6F42C1; }
-#row-alt { background: #F6F8FA; }
-#base-bg { background: $light-bg; }
-#base-fg { color: $light-fg; }
+#success { color: #B8BB26; }
+#warning { color: #FABD2F; }
+#error { color: #FB4934; }
+#muted { color: #A89984; }
+#selection { color: white; background: #504945; text-style: bold; }
+#info { color: #83A598; }
+#notice { color: #D3869B; }
+#row-alt { background: #32302F; }
+#base-bg { background: $gruvbox-bg; }
+#base-fg { color: $gruvbox-fg; }
/* Content tokens — labels, change indicators, search highlight */
-#label { color: #735C00; }
-#change { color: #B08800; }
-#search-match { color: black; background: #FFD33D; }
-#mnemonic { color: #735C00; text-style: bold underline; }
+#label { color: #FABD2F; }
+#change { color: #FABD2F; }
+#search-match { color: black; background: #FABD2F; }
+#mnemonic { color: #FABD2F; text-style: bold underline; }
/* Diagram tokens */
-#diagram-border { color: #808080; }
-#diagram-id { color: $light-fg; }
-#diagram-from { color: #22863A; }
-#diagram-to { color: #0366D6; }
-#diagram-choice { color: #B08800; }
-#diagram-action { color: #6F42C1; }
-#diagram-eip { color: #5A32A3; }
-#diagram-default { color: #6A737D; }
+#diagram-border { color: #A89984; }
+#diagram-id { color: $gruvbox-fg; }
+#diagram-from { color: #B8BB26; }
+#diagram-to { color: #83A598; }
+#diagram-choice { color: #FABD2F; }
+#diagram-action { color: #D3869B; }
+#diagram-eip { color: #8EC07C; }
+#diagram-default { color: #A89984; }
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/kanagawa.tcss
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/kanagawa.tcss
new file mode 100644
index 000000000000..fa12fdd01a4d
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/kanagawa.tcss
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+/* Camel TUI Kanagawa theme. Dark theme inspired by Katsushika Hokusai's
famous wave painting. */
+
+$brand: #7E9CD8;
+$kanagawa-bg: #1F1F28;
+$kanagawa-fg: #DCD7BA;
+
+#accent { color: $brand; }
+#accent-bg { color: white; background: $brand; text-style: bold; }
+#hint-key { color: white; background: $brand; text-style: bold; }
+#border { color: #727169; }
+#border-focused { color: $brand; }
+#title { color: $brand; text-style: bold; }
+#success { color: #98BB6C; }
+#warning { color: #E6C384; }
+#error { color: #E46876; }
+#muted { color: #727169; }
+#selection { color: white; background: #2A2A37; text-style: bold; }
+#info { color: #7E9CD8; }
+#notice { color: #957FB8; }
+#row-alt { background: #2A2A37; }
+#base-bg { background: $kanagawa-bg; }
+#base-fg { color: $kanagawa-fg; }
+
+/* Content tokens — labels, change indicators, search highlight */
+#label { color: #E6C384; }
+#change { color: #E6C384; }
+#search-match { color: black; background: #E6C384; }
+#mnemonic { color: #E6C384; text-style: bold underline; }
+
+/* Diagram tokens */
+#diagram-border { color: #727169; }
+#diagram-id { color: $kanagawa-fg; }
+#diagram-from { color: #98BB6C; }
+#diagram-to { color: #7E9CD8; }
+#diagram-choice { color: #E6C384; }
+#diagram-action { color: #D27E99; }
+#diagram-eip { color: #957FB8; }
+#diagram-default { color: #727169; }
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/light.tcss
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/light.tcss
index a6a9a285a348..a00d708b39d5 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/light.tcss
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/light.tcss
@@ -16,13 +16,13 @@
*/
/* Camel TUI light theme. Brand orange stays truecolor; status hues darkened
for light terminals. */
-$brand: #F69123;
+$brand: #D97706;
$light-bg: #FFFFFF;
$light-fg: #24292E;
#accent { color: $brand; }
#accent-bg { color: white; background: $brand; text-style: bold; }
-#hint-key { color: black; background: $brand; text-style: bold; }
+#hint-key { color: black; background: #F0A030; text-style: bold; }
#border { color: #C0C0C0; }
#border-focused { color: $brand; }
#title { color: $brand; text-style: bold; }
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/monochrome.tcss
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/monochrome.tcss
new file mode 100644
index 000000000000..1c48b34ce09d
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/monochrome.tcss
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+/* Camel TUI monochrome theme — no color, brightness only. */
+
+$brand: #FFFFFF;
+$dark-bg: #1A1A1A;
+$dark-fg: #C8C8C8;
+
+#accent { color: $brand; }
+#accent-bg { color: black; background: $brand; text-style: bold; }
+#hint-key { color: black; background: #B0B0B0; text-style: bold; }
+#border { color: #404040; }
+#border-focused { color: $brand; }
+#title { color: $brand; text-style: bold; }
+#success { color: #A0A0A0; }
+#warning { color: #D0D0D0; }
+#error { color: #FFFFFF; text-style: bold; }
+#muted { color: #606060; }
+#selection { color: white; background: #505050; text-style: bold; }
+#info { color: #B0B0B0; }
+#notice { color: #909090; }
+#row-alt { background: #222222; }
+#base-bg { background: $dark-bg; }
+#base-fg { color: $dark-fg; }
+
+/* Content tokens */
+#label { color: #D0D0D0; }
+#change { color: #D0D0D0; }
+#search-match { color: black; background: #D0D0D0; }
+#mnemonic { color: #D0D0D0; text-style: bold underline; }
+
+/* Diagram tokens */
+#diagram-border { color: #606060; }
+#diagram-id { color: $dark-fg; }
+#diagram-from { color: #B0B0B0; }
+#diagram-to { color: #909090; }
+#diagram-choice { color: #D0D0D0; }
+#diagram-action { color: #808080; }
+#diagram-eip { color: #707070; }
+#diagram-default { color: #606060; }
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/light.tcss
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/nord.tcss
similarity index 51%
copy from
dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/light.tcss
copy to
dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/nord.tcss
index a6a9a285a348..b32e287beeee 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/light.tcss
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/nord.tcss
@@ -14,41 +14,41 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/* Camel TUI light theme. Brand orange stays truecolor; status hues darkened
for light terminals. */
+/* Camel TUI Nord theme. Muted arctic palette inspired by the Nord color
scheme. */
-$brand: #F69123;
-$light-bg: #FFFFFF;
-$light-fg: #24292E;
+$brand: #88C0D0;
+$nord-bg: #2E3440;
+$nord-fg: #D8DEE9;
#accent { color: $brand; }
#accent-bg { color: white; background: $brand; text-style: bold; }
#hint-key { color: black; background: $brand; text-style: bold; }
-#border { color: #C0C0C0; }
+#border { color: #4C566A; }
#border-focused { color: $brand; }
#title { color: $brand; text-style: bold; }
-#success { color: #22863A; }
-#warning { color: #B08800; }
-#error { color: #D73A49; }
-#muted { color: #6A737D; }
-#selection { color: white; background: #0366D6; text-style: bold; }
-#info { color: #0366D6; }
-#notice { color: #6F42C1; }
-#row-alt { background: #F6F8FA; }
-#base-bg { background: $light-bg; }
-#base-fg { color: $light-fg; }
+#success { color: #A3BE8C; }
+#warning { color: #EBCB8B; }
+#error { color: #BF616A; }
+#muted { color: #4C566A; }
+#selection { color: white; background: #3B4252; text-style: bold; }
+#info { color: #88C0D0; }
+#notice { color: #B48EAD; }
+#row-alt { background: #333947; }
+#base-bg { background: $nord-bg; }
+#base-fg { color: $nord-fg; }
/* Content tokens — labels, change indicators, search highlight */
-#label { color: #735C00; }
-#change { color: #B08800; }
-#search-match { color: black; background: #FFD33D; }
-#mnemonic { color: #735C00; text-style: bold underline; }
+#label { color: #EBCB8B; }
+#change { color: #EBCB8B; }
+#search-match { color: black; background: #EBCB8B; }
+#mnemonic { color: #EBCB8B; text-style: bold underline; }
/* Diagram tokens */
-#diagram-border { color: #808080; }
-#diagram-id { color: $light-fg; }
-#diagram-from { color: #22863A; }
-#diagram-to { color: #0366D6; }
-#diagram-choice { color: #B08800; }
-#diagram-action { color: #6F42C1; }
-#diagram-eip { color: #5A32A3; }
-#diagram-default { color: #6A737D; }
+#diagram-border { color: #4C566A; }
+#diagram-id { color: $nord-fg; }
+#diagram-from { color: #A3BE8C; }
+#diagram-to { color: #88C0D0; }
+#diagram-choice { color: #EBCB8B; }
+#diagram-action { color: #B48EAD; }
+#diagram-eip { color: #5E81AC; }
+#diagram-default { color: #4C566A; }
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/light.tcss
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/rose-pine.tcss
similarity index 51%
copy from
dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/light.tcss
copy to
dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/rose-pine.tcss
index a6a9a285a348..1a4aab49fb32 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/light.tcss
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/rose-pine.tcss
@@ -14,41 +14,41 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/* Camel TUI light theme. Brand orange stays truecolor; status hues darkened
for light terminals. */
+/* Camel TUI Rosé Pine theme. Soho vibes with muted tones on a dark base. */
-$brand: #F69123;
-$light-bg: #FFFFFF;
-$light-fg: #24292E;
+$brand: #EBBCBA;
+$rosepine-bg: #191724;
+$rosepine-fg: #E0DEF4;
#accent { color: $brand; }
#accent-bg { color: white; background: $brand; text-style: bold; }
#hint-key { color: black; background: $brand; text-style: bold; }
-#border { color: #C0C0C0; }
+#border { color: #6E6A86; }
#border-focused { color: $brand; }
#title { color: $brand; text-style: bold; }
-#success { color: #22863A; }
-#warning { color: #B08800; }
-#error { color: #D73A49; }
-#muted { color: #6A737D; }
-#selection { color: white; background: #0366D6; text-style: bold; }
-#info { color: #0366D6; }
-#notice { color: #6F42C1; }
-#row-alt { background: #F6F8FA; }
-#base-bg { background: $light-bg; }
-#base-fg { color: $light-fg; }
+#success { color: #9CCFD8; }
+#warning { color: #F6C177; }
+#error { color: #EB6F92; }
+#muted { color: #6E6A86; }
+#selection { color: white; background: #26233A; text-style: bold; }
+#info { color: #EBBCBA; }
+#notice { color: #C4A7E7; }
+#row-alt { background: #1F1D2E; }
+#base-bg { background: $rosepine-bg; }
+#base-fg { color: $rosepine-fg; }
/* Content tokens — labels, change indicators, search highlight */
-#label { color: #735C00; }
-#change { color: #B08800; }
-#search-match { color: black; background: #FFD33D; }
-#mnemonic { color: #735C00; text-style: bold underline; }
+#label { color: #F6C177; }
+#change { color: #F6C177; }
+#search-match { color: black; background: #F6C177; }
+#mnemonic { color: #F6C177; text-style: bold underline; }
/* Diagram tokens */
-#diagram-border { color: #808080; }
-#diagram-id { color: $light-fg; }
-#diagram-from { color: #22863A; }
-#diagram-to { color: #0366D6; }
-#diagram-choice { color: #B08800; }
-#diagram-action { color: #6F42C1; }
-#diagram-eip { color: #5A32A3; }
-#diagram-default { color: #6A737D; }
+#diagram-border { color: #6E6A86; }
+#diagram-id { color: $rosepine-fg; }
+#diagram-from { color: #9CCFD8; }
+#diagram-to { color: #EBBCBA; }
+#diagram-choice { color: #F6C177; }
+#diagram-action { color: #EB6F92; }
+#diagram-eip { color: #C4A7E7; }
+#diagram-default { color: #6E6A86; }
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/light.tcss
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/solarized-dark.tcss
similarity index 51%
copy from
dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/light.tcss
copy to
dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/solarized-dark.tcss
index a6a9a285a348..02c270178775 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/light.tcss
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/solarized-dark.tcss
@@ -14,41 +14,41 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/* Camel TUI light theme. Brand orange stays truecolor; status hues darkened
for light terminals. */
+/* Camel TUI Solarized Dark theme. Ethan Schoonover's precision color palette
on a dark base. */
-$brand: #F69123;
-$light-bg: #FFFFFF;
-$light-fg: #24292E;
+$brand: #B58900;
+$solar-bg: #002B36;
+$solar-fg: #839496;
#accent { color: $brand; }
#accent-bg { color: white; background: $brand; text-style: bold; }
#hint-key { color: black; background: $brand; text-style: bold; }
-#border { color: #C0C0C0; }
+#border { color: #586E75; }
#border-focused { color: $brand; }
#title { color: $brand; text-style: bold; }
-#success { color: #22863A; }
-#warning { color: #B08800; }
-#error { color: #D73A49; }
-#muted { color: #6A737D; }
-#selection { color: white; background: #0366D6; text-style: bold; }
-#info { color: #0366D6; }
-#notice { color: #6F42C1; }
-#row-alt { background: #F6F8FA; }
-#base-bg { background: $light-bg; }
-#base-fg { color: $light-fg; }
+#success { color: #859900; }
+#warning { color: #B58900; }
+#error { color: #DC322F; }
+#muted { color: #586E75; }
+#selection { color: white; background: #073642; text-style: bold; }
+#info { color: #268BD2; }
+#notice { color: #6C71C4; }
+#row-alt { background: #073642; }
+#base-bg { background: $solar-bg; }
+#base-fg { color: $solar-fg; }
/* Content tokens — labels, change indicators, search highlight */
-#label { color: #735C00; }
-#change { color: #B08800; }
-#search-match { color: black; background: #FFD33D; }
-#mnemonic { color: #735C00; text-style: bold underline; }
+#label { color: #B58900; }
+#change { color: #B58900; }
+#search-match { color: black; background: #B58900; }
+#mnemonic { color: #B58900; text-style: bold underline; }
/* Diagram tokens */
-#diagram-border { color: #808080; }
-#diagram-id { color: $light-fg; }
-#diagram-from { color: #22863A; }
-#diagram-to { color: #0366D6; }
-#diagram-choice { color: #B08800; }
-#diagram-action { color: #6F42C1; }
-#diagram-eip { color: #5A32A3; }
-#diagram-default { color: #6A737D; }
+#diagram-border { color: #586E75; }
+#diagram-id { color: $solar-fg; }
+#diagram-from { color: #859900; }
+#diagram-to { color: #2AA198; }
+#diagram-choice { color: #B58900; }
+#diagram-action { color: #D33682; }
+#diagram-eip { color: #6C71C4; }
+#diagram-default { color: #586E75; }
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/solarized-light.tcss
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/solarized-light.tcss
new file mode 100644
index 000000000000..b607d7083ad6
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/solarized-light.tcss
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+/* Camel TUI Solarized Light theme. Ethan Schoonover's precision color palette
on a light base. */
+
+$brand: #268BD2;
+$solar-bg: #FDF6E3;
+$solar-fg: #657B83;
+
+#accent { color: $brand; }
+#accent-bg { color: white; background: $brand; text-style: bold; }
+#hint-key { color: white; background: #268BD2; text-style: bold; }
+#border { color: #93A1A1; }
+#border-focused { color: $brand; }
+#title { color: $brand; text-style: bold; }
+#success { color: #859900; }
+#warning { color: #B58900; }
+#error { color: #DC322F; }
+#muted { color: #93A1A1; }
+#selection { color: white; background: #268BD2; text-style: bold; }
+#info { color: #268BD2; }
+#notice { color: #6C71C4; }
+#row-alt { background: #EEE8D5; }
+#base-bg { background: $solar-bg; }
+#base-fg { color: $solar-fg; }
+
+/* Content tokens — labels, change indicators, search highlight */
+#label { color: #B58900; }
+#change { color: #B58900; }
+#search-match { color: #657B83; background: #B58900; }
+#mnemonic { color: #B58900; text-style: bold underline; }
+
+/* Diagram tokens */
+#diagram-border { color: #93A1A1; }
+#diagram-id { color: $solar-fg; }
+#diagram-from { color: #859900; }
+#diagram-to { color: #2AA198; }
+#diagram-choice { color: #B58900; }
+#diagram-action { color: #D33682; }
+#diagram-eip { color: #6C71C4; }
+#diagram-default { color: #93A1A1; }
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/tokyo-night.tcss
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/tokyo-night.tcss
new file mode 100644
index 000000000000..d84a93243c5b
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/tokyo-night.tcss
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+/* Camel TUI Tokyo Night theme. Cool blue-purple palette inspired by Tokyo
city lights. */
+
+$brand: #7AA2F7;
+$tokyo-bg: #1A1B26;
+$tokyo-fg: #A9B1D6;
+
+#accent { color: $brand; }
+#accent-bg { color: white; background: $brand; text-style: bold; }
+#hint-key { color: white; background: $brand; text-style: bold; }
+#border { color: #3B3F52; }
+#border-focused { color: $brand; }
+#title { color: $brand; text-style: bold; }
+#success { color: #9ECE6A; }
+#warning { color: #E0AF68; }
+#error { color: #F7768E; }
+#muted { color: #565F89; }
+#selection { color: white; background: #283B5E; text-style: bold; }
+#info { color: #7AA2F7; }
+#notice { color: #BB9AF7; }
+#row-alt { background: #1F2133; }
+#base-bg { background: $tokyo-bg; }
+#base-fg { color: $tokyo-fg; }
+
+/* Content tokens — labels, change indicators, search highlight */
+#label { color: #E0AF68; }
+#change { color: #E0AF68; }
+#search-match { color: black; background: #E0AF68; }
+#mnemonic { color: #E0AF68; text-style: bold underline; }
+
+/* Diagram tokens */
+#diagram-border { color: #565F89; }
+#diagram-id { color: $tokyo-fg; }
+#diagram-from { color: #9ECE6A; }
+#diagram-to { color: #2AC3DE; }
+#diagram-choice { color: #E0AF68; }
+#diagram-action { color: #BB9AF7; }
+#diagram-eip { color: #73DACA; }
+#diagram-default { color: #565F89; }
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ThemeModeCompletionCandidatesTest.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ThemeModeCompletionCandidatesTest.java
index 266a0d083901..3288e9f4276d 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ThemeModeCompletionCandidatesTest.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ThemeModeCompletionCandidatesTest.java
@@ -26,10 +26,13 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
class ThemeModeCompletionCandidatesTest {
@Test
- void offersDarkAndLightForShellCompletion() {
+ void offersAllThemeIdsForShellCompletion() {
List<String> candidates = new ArrayList<>();
new ThemeModeCompletionCandidates().forEach(candidates::add);
- assertEquals(List.of("dark", "light"), candidates);
+ assertEquals(List.of(
+ "dark", "light", "dracula", "nord", "solarized-dark",
"solarized-light",
+ "gruvbox-dark", "catppuccin-mocha", "catppuccin-latte",
"tokyo-night",
+ "rose-pine", "kanagawa", "everforest", "monochrome", "crt"),
candidates);
}
}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ThemeModeTest.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ThemeModeTest.java
index e3a104eece5e..eb98f28b2b4e 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ThemeModeTest.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ThemeModeTest.java
@@ -59,26 +59,47 @@ class ThemeModeTest {
}
@Test
- void toggleFlipsBetweenTheTwoModes() {
- assertEquals(ThemeMode.LIGHT, ThemeMode.DARK.toggle());
- assertEquals(ThemeMode.DARK, ThemeMode.LIGHT.toggle());
+ void nextCyclesToNextThemeInDeclarationOrder() {
+ assertEquals(ThemeMode.LIGHT, ThemeMode.DARK.next());
+ assertEquals(ThemeMode.DRACULA, ThemeMode.LIGHT.next());
+ assertEquals(ThemeMode.DARK, ThemeMode.CRT.next());
}
@Test
- void idsListsBothCanonicalValuesInDeclarationOrder() {
- assertEquals(List.of("dark", "light"), ThemeMode.ids());
+ void idsListsAllCanonicalValuesInDeclarationOrder() {
+ assertEquals(List.of(
+ "dark", "light", "dracula", "nord", "solarized-dark",
"solarized-light",
+ "gruvbox-dark", "catppuccin-mocha", "catppuccin-latte",
"tokyo-night",
+ "rose-pine", "kanagawa", "everforest", "monochrome", "crt"),
ThemeMode.ids());
}
@Test
void stylesheetResourcePointsAtTheModeSpecificTcssFile() {
assertEquals("tui/themes/dark.tcss",
ThemeMode.DARK.stylesheetResource());
assertEquals("tui/themes/light.tcss",
ThemeMode.LIGHT.stylesheetResource());
+ assertEquals("tui/themes/catppuccin-mocha.tcss",
ThemeMode.CATPPUCCIN_MOCHA.stylesheetResource());
+ assertEquals("tui/themes/tokyo-night.tcss",
ThemeMode.TOKYO_NIGHT.stylesheetResource());
}
@Test
void idIsDistinctLowercaseCanonicalFormPerMode() {
assertEquals("dark", ThemeMode.DARK.id());
assertEquals("light", ThemeMode.LIGHT.id());
+ assertEquals("catppuccin-mocha", ThemeMode.CATPPUCCIN_MOCHA.id());
assertNotEquals(ThemeMode.DARK.id(), ThemeMode.LIGHT.id());
}
+
+ @Test
+ void labelReturnsHumanReadableDisplayName() {
+ assertEquals("Dark", ThemeMode.DARK.label());
+ assertEquals("Catppuccin Mocha", ThemeMode.CATPPUCCIN_MOCHA.label());
+ assertEquals("Rosé Pine", ThemeMode.ROSE_PINE.label());
+ }
+
+ @Test
+ void parseRecognizesAllThemeIds() {
+ for (ThemeMode m : ThemeMode.values()) {
+ assertEquals(Optional.of(m), ThemeMode.parse(m.id()));
+ }
+ }
}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ThemeTest.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ThemeTest.java
index 306f3feda389..eac4f2e90391 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ThemeTest.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ThemeTest.java
@@ -82,7 +82,7 @@ class ThemeTest {
void lightPaletteDiffersForStatusTokensButKeepsBrand() {
// Light theme: status hues are explicit dark hex; brand orange is
unchanged.
Theme.setMode("light");
- assertEquals(Color.rgb(0xF6, 0x91, 0x23), Theme.accent());
+ assertEquals(Color.rgb(0xD9, 0x77, 0x06), Theme.accent());
assertEquals(Style.EMPTY.fg(Color.rgb(0x22, 0x86, 0x3A)),
Theme.success());
assertEquals(Style.EMPTY.fg(Color.rgb(0xC0, 0xC0, 0xC0)),
Theme.border());
// Zebra background is theme-aware: light gray on light, unlike the
dark gray used on dark.
@@ -93,7 +93,7 @@ class ThemeTest {
}
@Test
- void toggleFlipsModeAndChangesThemeDependentToken() {
+ void toggleCyclesToNextThemeAndChangesToken() {
Theme.setMode("dark");
assertEquals("dark", Theme.mode());
Style darkBorder = Theme.border();
@@ -106,12 +106,10 @@ class ThemeTest {
}
@Test
- void toggleFlipsModeBackAndForth() {
+ void toggleCyclesThroughAllThemes() {
Theme.setMode("dark");
assertEquals("light", Theme.toggle());
- assertEquals("light", Theme.mode());
- assertEquals("dark", Theme.toggle());
- assertEquals("dark", Theme.mode());
+ assertEquals("dracula", Theme.toggle());
}
@Test
@@ -142,9 +140,12 @@ class ThemeTest {
}
@Test
- void isValidModeAcceptsDarkAndLightOnly() {
+ void isValidModeAcceptsAllKnownThemes() {
assertTrue(Theme.isValidMode("dark"));
assertTrue(Theme.isValidMode("LIGHT"));
+ assertTrue(Theme.isValidMode("dracula"));
+ assertTrue(Theme.isValidMode("catppuccin-mocha"));
+ assertTrue(Theme.isValidMode("tokyo-night"));
assertFalse(Theme.isValidMode("sepia"));
assertFalse(Theme.isValidMode(null));
}
@@ -219,6 +220,60 @@ class ThemeTest {
}
}
+ @Test
+ void previewAppliesThemeWithoutPersisting() {
+ Theme.setMode("dark");
+ Theme.preview("dracula");
+ assertEquals("dracula", Theme.mode());
+ assertEquals("dark", Theme.persistedMode());
+ }
+
+ @Test
+ void revertPreviewRestoresOriginalTheme() {
+ Theme.setMode("dark");
+ Theme.preview("nord");
+ assertEquals("nord", Theme.mode());
+
+ Theme.revertPreview();
+ assertEquals("dark", Theme.mode());
+ assertEquals("dark", Theme.persistedMode());
+ }
+
+ @Test
+ void confirmPreviewClearsPreviewState() {
+ Theme.setMode("dark");
+ Theme.preview("tokyo-night");
+ Theme.confirmPreview();
+ assertEquals("tokyo-night", Theme.mode());
+ assertEquals("tokyo-night", Theme.persistedMode());
+ }
+
+ @Test
+ void previewSequenceTracksOnlyOriginal() {
+ Theme.setMode("dark");
+ Theme.preview("dracula");
+ Theme.preview("nord");
+ Theme.preview("catppuccin-mocha");
+ assertEquals("dark", Theme.persistedMode());
+
+ Theme.revertPreview();
+ assertEquals("dark", Theme.mode());
+ }
+
+ @Test
+ void allThemesLoadSuccessfully() {
+ for (ThemeMode m : ThemeMode.values()) {
+ Theme.resetForTesting();
+ Theme.setMode(m.id());
+ assertEquals(m.id(), Theme.mode());
+ assertNotNull(Theme.accent());
+ assertNotNull(Theme.baseBg());
+ assertNotNull(Theme.baseFg());
+ assertNotNull(Theme.success());
+ assertNotNull(Theme.error());
+ }
+ }
+
@Test
void accessorsNeverReturnNull() {
// Resilience: even when resolution is exercised the palette is always
usable.