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 54307da230f9 CAMEL-23839: camel-jbang TUI - switchable light/dark CSS 
theme
54307da230f9 is described below

commit 54307da230f9a31e13ce158b1d686ac580119718
Author: Adriano Machado <[email protected]>
AuthorDate: Wed Jul 1 08:41:21 2026 -0400

    CAMEL-23839: camel-jbang TUI - switchable light/dark CSS theme
    
    Introduce CSS-backed light/dark theme for the TUI, replacing hard-coded
    inline colors with a central semantic Theme. Two stylesheets ship
    (dark.tcss / light.tcss) toggled at runtime with F4, persisted in user
    config. Includes visual polish: active-tab highlight, zebra rows,
    focus-aware borders, shared empty-state, and MCP indicator theming.
    
    Co-Authored-By: Claude Opus 4.8 <[email protected]>
---
 .../ROOT/pages/camel-4x-upgrade-guide-4_21.adoc    |   3 +
 .../modules/ROOT/pages/camel-jbang-tui.adoc        |  12 +
 dsl/camel-jbang/camel-jbang-plugin-tui/pom.xml     |   5 +
 .../dsl/jbang/core/commands/tui/ActionsPopup.java  |  12 +-
 .../dsl/jbang/core/commands/tui/AiLogPopup.java    |   2 +-
 .../dsl/jbang/core/commands/tui/CamelMonitor.java  |  67 +++--
 .../jbang/core/commands/tui/CaptionOverlay.java    |   2 +-
 .../dsl/jbang/core/commands/tui/ClasspathTab.java  |   2 +-
 .../core/commands/tui/DataRefreshService.java      |  22 +-
 .../dsl/jbang/core/commands/tui/ErrorsTab.java     |   2 +-
 .../dsl/jbang/core/commands/tui/FormHelper.java    |   2 +-
 .../dsl/jbang/core/commands/tui/HelpOverlay.java   |   4 +-
 .../dsl/jbang/core/commands/tui/HistoryTab.java    |   2 +-
 .../camel/dsl/jbang/core/commands/tui/HttpTab.java |   2 +-
 .../dsl/jbang/core/commands/tui/McpFacade.java     |   5 +-
 .../dsl/jbang/core/commands/tui/McpLogPopup.java   |   2 +-
 .../jbang/core/commands/tui/MonitorContext.java    |  36 ++-
 .../dsl/jbang/core/commands/tui/OverviewTab.java   |  73 ++++-
 .../jbang/core/commands/tui/RunOptionsForm.java    |   6 +-
 .../jbang/core/commands/tui/SearchHighlighter.java |   6 +-
 .../jbang/core/commands/tui/SendMessagePopup.java  |   2 +-
 .../dsl/jbang/core/commands/tui/ShellPanel.java    |  18 +-
 .../camel/dsl/jbang/core/commands/tui/Theme.java   | 309 +++++++++++++++++++++
 .../src/main/resources/tui/themes/dark.tcss        |  37 +++
 .../src/main/resources/tui/themes/light.tcss       |  37 +++
 .../core/commands/tui/ShellPanelColorTest.java     |   2 +-
 .../core/commands/tui/SyntaxHighlighterTest.java   |   3 +-
 .../dsl/jbang/core/commands/tui/ThemeTest.java     | 145 ++++++++++
 pom.xml                                            |   1 +
 29 files changed, 724 insertions(+), 97 deletions(-)

diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc
index 4e8e7d3121d3..2b7ffa53e1b4 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc
@@ -32,6 +32,9 @@ The project exported by `camel export` now includes 
additional guidance for AI c
 `readme.md` gained a _For AI coding assistants_ section linking to the Apache 
Camel website LLM index,
 and a new `AGENTS.md` file is generated at the project root. This applies to 
all runtimes (Camel Main, Spring Boot and Quarkus).
 
+The Camel TUI (`camel tui`) theme is now backed by CSS stylesheets and ships a 
switchable light/dark palette.
+Press *F4* to toggle at runtime; the selection is persisted as 
`camel.tui.theme` in `.camel-jbang-user.properties`.
+
 === camel-micrometer
 
 The `MicrometerExchangeEventNotifier` now always includes the `routeId` tag on 
exchange event metrics.
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 bfe2225b3b38..f2dc24b20ab6 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-tui.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-tui.adoc
@@ -303,6 +303,17 @@ The Doctor checks your development environment and reports 
issues:
 * Common port conflicts (8080, 8443, 9090)
 * Disk space in temp directory
 
+== Theme
+
+The TUI ships with two color themes, *dark* (the default) and *light*, defined 
as
+CSS stylesheets. Press *F4* on any screen to toggle between them at runtime.
+
+The brand orange accent is identical in both themes; status colors (success,
+warning, error) and borders adapt for readability on dark and light terminals.
+
+Your choice is remembered: it is saved as `camel.tui.theme` (`dark` or `light`)
+in `.camel-jbang-user.properties` and restored the next time you open the TUI.
+
 == Keyboard Shortcuts
 
 === Global (All Tabs)
@@ -316,6 +327,7 @@ The Doctor checks your development environment and reports 
issues:
 | *F1* / *?* | Context-sensitive help (toggle)
 | *F2* | Actions menu
 | *F3* | Switch between integrations (when multiple running)
+| *F4* | Toggle light / dark theme
 | *Shift+F5* | Take screenshot
 | *Ctrl+R* | Start/stop tape recording
 | *Ctrl+C* / *Q* | Quit
diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/pom.xml 
b/dsl/camel-jbang/camel-jbang-plugin-tui/pom.xml
index 62014014c056..fc0f12a27cc5 100644
--- a/dsl/camel-jbang/camel-jbang-plugin-tui/pom.xml
+++ b/dsl/camel-jbang/camel-jbang-plugin-tui/pom.xml
@@ -58,6 +58,11 @@
             <artifactId>tamboui-tui</artifactId>
             <version>${tamboui-version}</version>
         </dependency>
+        <dependency>
+            <groupId>dev.tamboui</groupId>
+            <artifactId>tamboui-css</artifactId>
+            <version>${tamboui-version}</version>
+        </dependency>
         <dependency>
             <groupId>dev.tamboui</groupId>
             <artifactId>tamboui-widgets</artifactId>
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 cce76250427d..38823a0bca25 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
@@ -497,7 +497,7 @@ class ActionsPopup {
             } else if (ke.isConfirm()) {
                 selectInfraService();
             } else if (ke.code() == KeyCode.CHAR) {
-                jumpToInfraService(ke.character());
+                jumpToInfraService(ke.string().charAt(0));
             }
             return true;
         }
@@ -1346,7 +1346,7 @@ class ActionsPopup {
         } else if (ke.isEnd()) {
             folderInputState.moveCursorToEnd();
         } else if (ke.code() == KeyCode.CHAR) {
-            folderInputState.insert(ke.character());
+            folderInputState.insert(ke.string().charAt(0));
         }
     }
 
@@ -1853,9 +1853,9 @@ class ActionsPopup {
             String impl = 
selectedInfraService.implementations.get(infraImplIndex);
             Rect implArea = new Rect(ix + labelW, row, fieldW, 1);
             frame.renderWidget(Paragraph.from(Line.from(
-                    Span.styled("◀ ", MonitorContext.HINT_KEY_STYLE),
+                    Span.styled("◀ ", Theme.hintKey()),
                     Span.raw(impl),
-                    Span.styled(" ▶", MonitorContext.HINT_KEY_STYLE))), 
implArea);
+                    Span.styled(" ▶", Theme.hintKey()))), implArea);
             row++;
         }
 
@@ -1887,8 +1887,8 @@ class ActionsPopup {
             infraPortState.moveCursorToStart();
         } else if (ke.isEnd()) {
             infraPortState.moveCursorToEnd();
-        } else if (ke.code() == KeyCode.CHAR && 
Character.isDigit(ke.character())) {
-            infraPortState.insert(ke.character());
+        } else if (ke.code() == KeyCode.CHAR && 
Character.isDigit(ke.string().charAt(0))) {
+            infraPortState.insert(ke.string().charAt(0));
         }
     }
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AiLogPopup.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AiLogPopup.java
index 147c75a51a69..d482f8247e29 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AiLogPopup.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AiLogPopup.java
@@ -104,7 +104,7 @@ class AiLogPopup {
                     .borderType(BorderType.ROUNDED).borders(Borders.ALL)
                     .title(" AI Log ")
                     .titleBottom(Title.from(Line.from(
-                            Span.styled(" Esc", 
MonitorContext.HINT_KEY_STYLE), Span.raw(" back "))))
+                            Span.styled(" Esc", Theme.hintKey()), Span.raw(" 
back "))))
                     .build();
             frame.renderWidget(block, popup);
             Rect inner = block.inner(popup);
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 37d15040f9c9..fd2b9b9c0339 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
@@ -131,7 +131,7 @@ public class CamelMonitor extends CamelCommand {
     private final FilesBrowser filesBrowser = new FilesBrowser();
     private PopupManager popupManager;
 
-    private ClassLoader classLoader;
+    private final ClassLoader classLoader;
 
     public CamelMonitor(CamelJBangMain main, ClassLoader classLoader) {
         super(main);
@@ -595,6 +595,10 @@ public class CamelMonitor extends CamelCommand {
             }
             return true;
         }
+        if (!textEditing && ke.isKey(KeyCode.F4)) {
+            Theme.toggle();
+            return true;
+        }
         if (ke.isKey(KeyCode.F5) && ke.hasShift()) {
             recordingManager.takeScreenshot();
             return true;
@@ -851,28 +855,28 @@ public class CamelMonitor extends CamelCommand {
         long activeCount = infos.stream().filter(i -> !i.vanishing).count();
 
         List<Span> titleSpans = new ArrayList<>();
-        titleSpans.add(Span.styled(" Camel TUI", 
Style.EMPTY.fg(Color.rgb(0xF6, 0x91, 0x23)).bold()));
+        titleSpans.add(Span.styled(" Camel TUI", Theme.title()));
         titleSpans.add(Span.raw("  "));
-        titleSpans.add(Span.styled(camelVersion != null ? "v" + camelVersion : 
"", Style.EMPTY.fg(Color.GREEN)));
+        titleSpans.add(Span.styled(camelVersion != null ? "v" + camelVersion : 
"", Theme.success()));
         titleSpans.add(Span.raw("  "));
-        titleSpans.add(Span.styled(activeCount + " integration(s)", 
Style.EMPTY.fg(Color.CYAN)));
+        titleSpans.add(Span.styled(activeCount + " integration(s)", 
Theme.info()));
         long activeInfra = dataService.infraData().get().stream().filter(i -> 
!i.vanishing).count();
         if (activeInfra > 0) {
             titleSpans.add(Span.raw("  "));
-            titleSpans.add(Span.styled(activeInfra + " infra(s)", 
Style.EMPTY.fg(Color.MAGENTA)));
+            titleSpans.add(Span.styled(activeInfra + " infra(s)", 
Theme.notice()));
         }
         if (ctx.selectedPid != null) {
             titleSpans.add(Span.raw("  "));
             InfraInfo selInfra = findSelectedInfra();
             if (selInfra != null) {
-                titleSpans.add(Span.styled("selected: " + selectedName(), 
Style.EMPTY.fg(Color.MAGENTA)));
+                titleSpans.add(Span.styled("selected: " + selectedName(), 
Theme.notice()));
             } else {
-                titleSpans.add(Span.styled("selected: " + selectedName(), 
Style.EMPTY.fg(Color.YELLOW)));
+                titleSpans.add(Span.styled("selected: " + selectedName(), 
Theme.warning()));
             }
         }
         if (actionsPopup.notification() != null) {
             titleSpans.add(Span.raw("  "));
-            Style style = actionsPopup.notificationError() ? 
Style.EMPTY.fg(Color.RED) : Style.EMPTY.fg(Color.GREEN);
+            Style style = actionsPopup.notificationError() ? Theme.error() : 
Theme.success();
             titleSpans.add(Span.styled(actionsPopup.notification(), style));
         }
         if (monitorNotification != null) {
@@ -880,7 +884,7 @@ public class CamelMonitor extends CamelCommand {
                 monitorNotification = null;
             } else {
                 titleSpans.add(Span.raw("  "));
-                Style style = monitorNotificationError ? 
Style.EMPTY.fg(Color.RED) : Style.EMPTY.fg(Color.GREEN);
+                Style style = monitorNotificationError ? Theme.error() : 
Theme.success();
                 titleSpans.add(Span.styled(monitorNotification, style));
             }
         }
@@ -933,7 +937,7 @@ public class CamelMonitor extends CamelCommand {
     private void renderTabs(Frame frame, Rect area) {
         boolean compact = area.width() < TABS_FULL_MIN_WIDTH;
         String dividerStr = compact ? "|" : " | ";
-        Span divider = Span.styled(dividerStr, Style.EMPTY.dim());
+        Span divider = Span.styled(dividerStr, Theme.muted());
         boolean infraSelected = isInfraSelected();
 
         if (infraSelected) {
@@ -954,7 +958,7 @@ public class CamelMonitor extends CamelCommand {
 
             Tabs tabs = Tabs.builder()
                     .titles(labels)
-                    .highlightStyle(Style.EMPTY.fg(Color.rgb(0xF6, 0x91, 
0x23)).bold())
+                    .highlightStyle(Theme.accentBg())
                     .divider(divider)
                     .build();
 
@@ -994,7 +998,7 @@ public class CamelMonitor extends CamelCommand {
 
         Tabs tabs = Tabs.builder()
                 .titles(labels)
-                .highlightStyle(Style.EMPTY.fg(Color.rgb(0xF6, 0x91, 
0x23)).bold())
+                .highlightStyle(Theme.accentBg())
                 .divider(divider)
                 .build();
 
@@ -1253,8 +1257,8 @@ public class CamelMonitor extends CamelCommand {
                     if (cmdOpt.isPresent() && argsOpt.isPresent() && 
argsOpt.get().length > 0) {
                         cmd.add(cmdOpt.get());
                         Collections.addAll(cmd, argsOpt.get());
-                    } else if (cmdLineOpt.isPresent()) {
-                        cmd.addAll(parseCommandLine(cmdLineOpt.get()));
+                    } else {
+                        cmdLineOpt.ifPresent(s -> 
cmd.addAll(parseCommandLine(s)));
                     }
 
                     if (cmd.isEmpty()) {
@@ -1346,7 +1350,7 @@ public class CamelMonitor extends CamelCommand {
         String msg = recordingManager.screenshotFlashMessage();
         if (msg != null) {
             frame.renderWidget(
-                    Paragraph.from(Line.from(Span.styled(" " + msg, 
Style.EMPTY.fg(Color.GREEN)))),
+                    Paragraph.from(Line.from(Span.styled(" " + msg, 
Theme.success()))),
                     area);
             return;
         }
@@ -1401,8 +1405,8 @@ public class CamelMonitor extends CamelCommand {
             for (RecordingManager.KeyRecord kr : visible) {
                 long age = now - kr.timestamp();
                 Style style = age < 1000
-                        ? Style.EMPTY.fg(Color.WHITE).bold().onBlue()
-                        : Style.EMPTY.dim();
+                        ? Theme.selectionBg()
+                        : Theme.muted();
                 rightSpans.add(Span.styled(" " + kr.label() + " ", style));
             }
         }
@@ -1420,17 +1424,17 @@ public class CamelMonitor extends CamelCommand {
             if (client != null) {
                 suffix = active ? " ●" : " ○";
                 mcpLabel += " (" + client + ")";
-                labelStyle = Style.EMPTY.fg(Color.GREEN);
-                suffixStyle = Style.EMPTY.fg(active ? Color.GREEN : 
Color.DARK_GRAY);
+                labelStyle = Theme.success();
+                suffixStyle = active ? Theme.mcpActive() : Theme.mcpIdle();
             } else {
                 suffix = " ✗";
-                labelStyle = Style.EMPTY.dim();
-                suffixStyle = Style.EMPTY.fg(Color.RED);
+                labelStyle = Theme.muted();
+                suffixStyle = Theme.mcpDown();
             }
             rightSpans.add(Span.styled(mcpLabel, labelStyle));
             rightSpans.add(Span.styled(suffix, suffixStyle));
             if (client == null) {
-                rightSpans.add(Span.styled("  F2 → Setup AI", 
Style.EMPTY.dim()));
+                rightSpans.add(Span.styled("  F2 → Setup AI", Theme.muted()));
             }
         }
 
@@ -1476,6 +1480,7 @@ public class CamelMonitor extends CamelCommand {
         }
         hint(fKeySpans, "F6", "shell");
         hint(fKeySpans, "F8", "AI");
+        hint(fKeySpans, "F4", "theme");
         spans.addAll(insertPos, fKeySpans);
         // Return total F-key span count. The footer drop loop uses this to 
remove pairs from
         // the tail (F6, then F3, F2), stopping before the first pair (F1 help 
when present).
@@ -1601,14 +1606,16 @@ public class CamelMonitor extends CamelCommand {
     private static Path writeMcpJson(int port) {
         Path path = Path.of(".mcp.json");
         try {
-            String json = "{\n"
-                          + "  \"mcpServers\": {\n"
-                          + "    \"camel-tui\": {\n"
-                          + "      \"type\": \"http\",\n"
-                          + "      \"url\": \"http://localhost:"; + port + 
"/mcp\"\n"
-                          + "    }\n"
-                          + "  }\n"
-                          + "}\n";
+            String json = """
+                    {
+                      "mcpServers": {
+                        "camel-tui": {
+                          "type": "http",
+                          "url": "http://localhost:%d/mcp";
+                        }
+                      }
+                    }
+                    """.formatted(port);
             Files.writeString(path, json);
             return path;
         } catch (IOException e) {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CaptionOverlay.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CaptionOverlay.java
index c728c4b1ecda..585658274a3a 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CaptionOverlay.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CaptionOverlay.java
@@ -107,7 +107,7 @@ class CaptionOverlay {
                     inlineLastKeystroke = System.currentTimeMillis();
                 }
             } else if (ke.code() == KeyCode.CHAR) {
-                inlineBuffer.append(ke.character());
+                inlineBuffer.append(ke.string());
                 captionText = inlineBuffer.toString();
                 inlineLastKeystroke = System.currentTimeMillis();
             }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ClasspathTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ClasspathTab.java
index ebb2c2deec63..538b2def9ff9 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ClasspathTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ClasspathTab.java
@@ -110,7 +110,7 @@ class ClasspathTab implements MonitorTab {
             return true;
         }
         if (ke.code() == KeyCode.CHAR) {
-            fuzzyFilter.appendChar(ke.character());
+            fuzzyFilter.appendChar(ke.string().charAt(0));
             refilter();
             return true;
         }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DataRefreshService.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DataRefreshService.java
index 1120c041aa92..6005b5aee136 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DataRefreshService.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DataRefreshService.java
@@ -331,12 +331,9 @@ class DataRefreshService {
             boolean stillAlive = infos.stream()
                     .anyMatch(i -> ctx.selectedPid.equals(i.pid) && 
!i.vanishing);
             if (!stillAlive) {
-                IntegrationInfo gone = infos.stream()
+                infos.stream()
                         .filter(i -> ctx.selectedPid.equals(i.pid))
-                        .findFirst().orElse(null);
-                if (gone != null) {
-                    ctx.lastSelectedName = gone.name;
-                }
+                        .findFirst().ifPresent(gone -> ctx.lastSelectedName = 
gone.name);
                 ctx.selectedPid = null;
             }
         }
@@ -368,10 +365,10 @@ class DataRefreshService {
         }
 
         if (ctx.selectedPid == null && !infraData.get().isEmpty()
-                && infos.stream().noneMatch(i -> !i.vanishing)) {
+                && infos.stream().allMatch(i -> i.vanishing)) {
             List<InfraInfo> infras = infraData.get();
             if (!infras.isEmpty()) {
-                int firstInfraIndex = infos.size() + (infras.size() > 0 ? 1 : 
0);
+                int firstInfraIndex = infos.size() + 1;
                 refreshCtx.onInfraAutoSelected(firstInfraIndex, 
infras.get(0).pid);
             }
         }
@@ -379,7 +376,6 @@ class DataRefreshService {
 
     // ---- Infra data ----
 
-    @SuppressWarnings("unchecked")
     private void refreshInfraData() {
         List<InfraInfo> infraInfos = new ArrayList<>();
         try {
@@ -493,7 +489,6 @@ class DataRefreshService {
         traces.set(allTraces);
     }
 
-    @SuppressWarnings("unchecked")
     private void readTraceFile(String pid, List<TraceEntry> allTraces) {
         Path traceFile = CommandLineHelper.getCamelDir().resolve(pid + 
"-trace.json");
         if (!Files.exists(traceFile)) {
@@ -531,6 +526,9 @@ class DataRefreshService {
                 }
                 try {
                     JsonObject json = (JsonObject) Jsoner.deserialize(line);
+                    if (json == null) {
+                        continue;
+                    }
                     Object tracesArray = json.get("traces");
                     if (tracesArray instanceof List<?> traceList) {
                         for (Object traceObj : traceList) {
@@ -559,7 +557,6 @@ class DataRefreshService {
 
     // ---- Span data ----
 
-    @SuppressWarnings("unchecked")
     void refreshSpanData() {
         String pid = ctx.selectedPid;
         if (pid == null) {
@@ -585,8 +582,8 @@ class DataRefreshService {
                     JsonArray arr = response.getCollection("spans");
                     if (arr != null) {
                         List<SpanEntry> entries = new ArrayList<>();
-                        for (int i = 0; i < arr.size(); i++) {
-                            JsonObject spanObj = (JsonObject) arr.get(i);
+                        for (Object o : arr) {
+                            JsonObject spanObj = (JsonObject) o;
                             entries.add(SpanEntry.fromJson(spanObj));
                         }
                         otelSpans.set(entries);
@@ -625,7 +622,6 @@ class DataRefreshService {
      * Load history data for the given PIDs and return the parsed entries. The 
caller is responsible for storing the
      * entries (e.g. on HistoryTab).
      */
-    @SuppressWarnings("unchecked")
     List<HistoryEntry> loadHistoryData(List<Long> pids) {
         List<HistoryEntry> allEntries = new ArrayList<>();
         for (Long pid : pids) {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java
index c8d142196c91..1aa94bbe4439 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java
@@ -671,7 +671,7 @@ class ErrorsTab implements MonitorTab {
     }
 
     private static void hintShowBhpv(List<Span> spans, boolean body, boolean 
headers, boolean props, boolean vars) {
-        spans.add(Span.styled(" show", HINT_KEY_STYLE));
+        spans.add(Span.styled(" show", Theme.hintKey()));
         spans.add(Span.raw(" "));
         spans.add(Span.styled(body ? "B" : "b", body ? 
Style.EMPTY.fg(Color.WHITE).bold() : Style.EMPTY.dim()));
         spans.add(Span.styled(headers ? "H" : "h", headers ? 
Style.EMPTY.fg(Color.WHITE).bold() : Style.EMPTY.dim()));
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FormHelper.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FormHelper.java
index 98db646f408f..5dcb71b56f3f 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FormHelper.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FormHelper.java
@@ -48,7 +48,7 @@ final class FormHelper {
         } else if (ke.isEnd()) {
             state.moveCursorToEnd();
         } else if (ke.code() == KeyCode.CHAR) {
-            state.insert(ke.character());
+            state.insert(ke.string().charAt(0));
         }
     }
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HelpOverlay.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HelpOverlay.java
index 6b616ea7e7ee..c375384e20a7 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HelpOverlay.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HelpOverlay.java
@@ -95,8 +95,8 @@ class HelpOverlay {
                 .borderType(BorderType.ROUNDED).borders(Borders.ALL)
                 .title(" Help ")
                 .titleBottom(Title.from(Line.from(
-                        Span.styled(" F1/?", MonitorContext.HINT_KEY_STYLE), 
Span.raw(" close "),
-                        Span.styled(" ↑↓", MonitorContext.HINT_KEY_STYLE), 
Span.raw(" scroll "))))
+                        Span.styled(" F1/? ", Theme.hintKey()), Span.raw(" 
close "),
+                        Span.styled(" ↑↓ ", Theme.hintKey()), Span.raw(" 
scroll "))))
                 .build();
 
         MarkdownView view = MarkdownView.builder()
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTab.java
index 1b4abc00bf92..80ed21e47f1c 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTab.java
@@ -1696,7 +1696,7 @@ class HistoryTab implements MonitorTab {
     }
 
     private static void hintShowBhpv(List<Span> spans, boolean body, boolean 
headers, boolean props, boolean vars) {
-        spans.add(Span.styled(" show", HINT_KEY_STYLE));
+        spans.add(Span.styled(" show", Theme.hintKey()));
         spans.add(Span.raw(" "));
         spans.add(Span.styled(body ? "B" : "b", body ? 
Style.EMPTY.fg(Color.WHITE).bold() : Style.EMPTY.dim()));
         spans.add(Span.styled(headers ? "H" : "h", headers ? 
Style.EMPTY.fg(Color.WHITE).bold() : Style.EMPTY.dim()));
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HttpTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HttpTab.java
index 01f17ef5afce..b8f8ac56556a 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HttpTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HttpTab.java
@@ -574,7 +574,7 @@ class HttpTab implements MonitorTab {
             return true;
         }
         if (ke.code() == KeyCode.CHAR) {
-            activeInput.insert(ke.character());
+            activeInput.insert(ke.string().charAt(0));
             return true;
         }
         return true;
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java
index 15a84d382290..674c4a3f0f3e 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java
@@ -433,7 +433,6 @@ class McpFacade {
         return tabRegistry.diagramTab().getTopologyDataAsJson();
     }
 
-    @SuppressWarnings("unchecked")
     JsonObject getSpanData(String traceId, int limit) {
         String pid = ctx.selectedPid;
         if (pid == null) {
@@ -465,8 +464,8 @@ class McpFacade {
                     JsonArray all = response.getCollection("spans");
                     if (all != null) {
                         JsonArray filtered = new JsonArray();
-                        for (int i = 0; i < all.size(); i++) {
-                            JsonObject span = (JsonObject) all.get(i);
+                        for (Object o : all) {
+                            JsonObject span = (JsonObject) o;
                             String tid = span.getString("traceId");
                             if (tid != null && tid.contains(traceId)) {
                                 filtered.add(span);
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpLogPopup.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpLogPopup.java
index dea6a75d2be9..6f0f6a469644 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpLogPopup.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpLogPopup.java
@@ -109,7 +109,7 @@ class McpLogPopup {
                     .borderType(BorderType.ROUNDED).borders(Borders.ALL)
                     .title(" MCP Log ")
                     .titleBottom(Title.from(Line.from(
-                            Span.styled(" Esc", 
MonitorContext.HINT_KEY_STYLE), Span.raw(" back "))))
+                            Span.styled(" Esc", Theme.hintKey()), Span.raw(" 
back "))))
                     .build();
             frame.renderWidget(block, popup);
             Rect inner = block.inner(popup);
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 18b2ae68d9c9..63a04388bab2 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
@@ -18,6 +18,7 @@ package org.apache.camel.dsl.jbang.core.commands.tui;
 
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -32,6 +33,7 @@ import dev.tamboui.tui.TuiRunner;
 import dev.tamboui.widgets.block.Block;
 import dev.tamboui.widgets.block.BorderType;
 import dev.tamboui.widgets.block.Borders;
+import dev.tamboui.widgets.block.Title;
 import dev.tamboui.widgets.paragraph.Paragraph;
 import dev.tamboui.widgets.table.Cell;
 import org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
@@ -43,7 +45,15 @@ import org.apache.camel.util.json.Jsoner;
  */
 class MonitorContext {
 
-    static final Style HINT_KEY_STYLE = Style.EMPTY.fg(Color.YELLOW).bold();
+    /** Small flat-orange camel for empty / no-selection states. */
+    static final String[] SMALL_CAMEL = {
+            " ,,__",
+            "/o.  \\___/\\",
+            "\\__/       \\",
+            "   |   |   |",
+            "   |   |   |~",
+            "  (_) (_) (_)",
+    };
 
     final AtomicReference<List<IntegrationInfo>> data;
     final AtomicReference<List<InfraInfo>> infraData;
@@ -128,23 +138,35 @@ class MonitorContext {
     }
 
     static void hint(List<Span> spans, String key, String label) {
-        spans.add(Span.styled(" " + key, HINT_KEY_STYLE));
+        spans.add(Span.styled(" " + key + " ", Theme.hintKey()));
         spans.add(Span.raw(" " + label + "  "));
     }
 
     static void hintLast(List<Span> spans, String key, String label) {
-        spans.add(Span.styled(" " + key, HINT_KEY_STYLE));
+        spans.add(Span.styled(" " + key + " ", Theme.hintKey()));
         spans.add(Span.raw(" " + label));
     }
 
     static void renderNoSelection(Frame frame, Rect area) {
+        List<Line> lines = new ArrayList<>();
+        lines.add(Line.from(Span.raw("")));
+        for (String row : SMALL_CAMEL) {
+            lines.add(Line.from(Span.styled("   " + row, 
Style.EMPTY.fg(Theme.accent()))));
+        }
+        lines.add(Line.from(Span.raw("")));
+        List<Span> hintSpans = new ArrayList<>();
+        hintSpans.add(Span.raw("   No integration selected.  "));
+        hint(hintSpans, "1", "Overview");
+        hint(hintSpans, "?", "Help");
+        lines.add(Line.from(hintSpans));
+
         frame.renderWidget(
                 Paragraph.builder()
-                        .text(Text.from(Line.from(
-                                Span.styled(" Select an integration from the 
Overview tab (press 1)",
-                                        Style.EMPTY.dim()))))
+                        .text(Text.from(lines))
                         
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
-                                .title(" No integration selected ").build())
+                                .title(Title.from(Line.from(
+                                        Span.styled(" No integration selected 
", Theme.title()))))
+                                .build())
                         .build(),
                 area);
     }
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 09b95d7995cf..ebd16308d1d6 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
@@ -199,6 +199,11 @@ class OverviewTab implements MonitorTab {
         int infraCount = infraInfos.size();
         dividerIndex = infraCount > 0 ? integrationCount : -1;
 
+        if (integrationCount == 0 && infraCount == 0) {
+            renderEmptyState(frame, area);
+            return;
+        }
+
         if (ctx.selectedPid != null) {
             for (int i = 0; i < infos.size(); i++) {
                 if (ctx.selectedPid.equals(infos.get(i).pid)) {
@@ -208,7 +213,7 @@ class OverviewTab implements MonitorTab {
             }
             for (int i = 0; i < infraInfos.size(); i++) {
                 if (ctx.selectedPid.equals(infraInfos.get(i).pid)) {
-                    int tableIndex = integrationCount + (dividerIndex >= 0 ? 1 
: 0) + i;
+                    int tableIndex = integrationCount + 1 + i;
                     tableState.select(tableIndex);
                     break;
                 }
@@ -232,7 +237,12 @@ class OverviewTab implements MonitorTab {
                 .split(area);
 
         List<Row> rows = new ArrayList<>();
+        int rowIndex = 0;
         for (IntegrationInfo info : infos) {
+            boolean isEven = (rowIndex++ % 2 == 0);
+            // Zebra striping at the row level so the selection highlight 
(patched on top) always wins.
+            Style rowBg = isEven ? Style.EMPTY.bg(Theme.zebra()) : Style.EMPTY;
+
             if (info.vanishing) {
                 long elapsed = System.currentTimeMillis() - info.vanishStart;
                 float fade = 1.0f - Math.min(1.0f, (float) elapsed / 
VANISH_DURATION_MS);
@@ -252,7 +262,7 @@ class OverviewTab implements MonitorTab {
                         Cell.from(Span.styled("", dimStyle)),
                         Cell.from(Span.styled("", dimStyle)),
                         Cell.from(Span.styled("", dimStyle)),
-                        Cell.from(Span.styled("", dimStyle))));
+                        Cell.from(Span.styled("", dimStyle))).style(rowBg));
             } else {
                 String stateText = extractState(info.state);
                 if (stoppingPids.contains(info.pid) || 
"Terminating".equals(stateText)) {
@@ -263,13 +273,12 @@ class OverviewTab implements MonitorTab {
                     stateText = "Stopped";
                 }
                 Style statusStyle = switch (stateText) {
-                    case "Started", "Running" -> Style.EMPTY.fg(Color.GREEN);
-                    case "Stopping" -> Style.EMPTY.fg(Color.YELLOW);
-                    case "Stopped" -> Style.EMPTY.fg(Color.LIGHT_RED);
-                    default -> Style.EMPTY.fg(Color.YELLOW);
+                    case "Started", "Running" -> Theme.success();
+                    case "Stopped" -> Theme.error();
+                    default -> Theme.warning();
                 };
 
-                Style failStyle = info.failed > 0 ? 
Style.EMPTY.fg(Color.LIGHT_RED).bold() : Style.EMPTY;
+                Style failStyle = info.failed > 0 ? Theme.error().bold() : 
Style.EMPTY;
 
                 String sinceLastDisplay = formatSinceLast(info);
 
@@ -296,7 +305,7 @@ class OverviewTab implements MonitorTab {
                         rightCell(String.valueOf(info.exchangesTotal), 8),
                         rightCell(String.valueOf(info.failed), 6, failStyle),
                         rightCell(String.valueOf(info.inflight), 8),
-                        Cell.from(sinceLastDisplay)));
+                        Cell.from(sinceLastDisplay)).style(rowBg));
             }
         }
 
@@ -324,6 +333,10 @@ class OverviewTab implements MonitorTab {
         }
 
         for (InfraInfo info : infraInfos) {
+            boolean isEven = (rowIndex++ % 2 == 0);
+            Style rowBg = isEven ? Style.EMPTY.bg(Theme.zebra()) : Style.EMPTY;
+            Style statusStyle = info.alive ? Theme.success() : Theme.error();
+
             if (info.vanishing) {
                 long elapsed = System.currentTimeMillis() - info.vanishStart;
                 float fade = 1.0f - Math.min(1.0f, (float) elapsed / 
VANISH_DURATION_MS);
@@ -342,9 +355,8 @@ class OverviewTab implements MonitorTab {
                         Cell.from(Span.styled("", dimStyle)),
                         Cell.from(Span.styled("", dimStyle)),
                         Cell.from(Span.styled("", dimStyle)),
-                        Cell.from(Span.styled("", dimStyle))));
+                        Cell.from(Span.styled("", dimStyle))).style(rowBg));
             } else {
-                Style statusStyle = info.alive ? Style.EMPTY.fg(Color.GREEN) : 
Style.EMPTY.fg(Color.LIGHT_RED);
                 String statusText = info.alive ? "Running" : "Stopped";
                 String infraAlias = "🔧  " + info.alias;
                 String version = info.serviceVersion != null ? 
info.serviceVersion : "";
@@ -360,7 +372,7 @@ class OverviewTab implements MonitorTab {
                         Cell.from(""),
                         Cell.from(""),
                         Cell.from(""),
-                        Cell.from("")));
+                        Cell.from("")).style(rowBg));
             }
         }
 
@@ -891,6 +903,7 @@ class OverviewTab implements MonitorTab {
                 - `S` — reverse sort order
                 - `F2` — actions menu
                 - `F3` — switch integration
+                - `F4` — toggle light/dark theme
                 """;
     }
 
@@ -926,4 +939,42 @@ class OverviewTab implements MonitorTab {
         result.put("selectedIndex", sel != null ? sel : -1);
         return result;
     }
+
+    private void renderEmptyState(Frame frame, Rect area) {
+        List<Line> lines = new ArrayList<>();
+        lines.add(Line.from(Span.raw("")));
+        for (String row : MonitorContext.SMALL_CAMEL) {
+            lines.add(Line.from(Span.styled("     " + row, 
Style.EMPTY.fg(Theme.accent()).bold())));
+        }
+        lines.add(Line.from(Span.styled("     No Active Camel Integrations 
Found", Theme.title())));
+        lines.add(Line.from(Span.raw("")));
+        lines.add(Line.from(Span.styled("  💡 How to monitor integrations:", 
Style.EMPTY.bold())));
+        lines.add(Line.from(Span.raw("     Run a route or integration in 
another terminal window:")));
+        lines.add(Line.from(Span.styled("     > camel run my-route.yaml", 
Theme.success())));
+        lines.add(Line.from(Span.raw("")));
+        lines.add(Line.from(Span.styled("  💻 Or use the embedded JLine shell 
panel:", Style.EMPTY.bold())));
+        lines.add(Line.from(List.of(
+                Span.raw("     Press "),
+                Span.styled(" F6 ", Theme.hintKey()),
+                Span.raw(" to open the shell and run commands directly, 
e.g.:"))));
+        lines.add(Line.from(Span.styled("     camel> run examples/demo.java", 
Theme.success())));
+        lines.add(Line.from(Span.raw("")));
+        lines.add(Line.from(List.of(
+                Span.styled("  ❔ For shortcut keys and documentation, press ", 
Theme.muted()),
+                Span.styled(" ? ", Theme.hintKey()),
+                Span.styled(" or ", Theme.muted()),
+                Span.styled(" F1 ", Theme.hintKey()),
+                Span.styled(".", Theme.muted()))));
+
+        frame.renderWidget(
+                Paragraph.builder()
+                        .text(Text.from(lines))
+                        .block(Block.builder()
+                                
.borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                                .title(Title.from(Line.from(
+                                        Span.styled(" Camel JBang TUI ", 
Theme.title()))))
+                                .build())
+                        .build(),
+                area);
+    }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RunOptionsForm.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RunOptionsForm.java
index b9cfdbb1aac2..ca29642fff48 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RunOptionsForm.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RunOptionsForm.java
@@ -657,11 +657,11 @@ class RunOptionsForm {
             active.moveCursorToEnd();
         } else if (ke.code() == KeyCode.CHAR) {
             if (digitsOnly) {
-                if (Character.isDigit(ke.character())) {
-                    active.insert(ke.character());
+                if (Character.isDigit(ke.string().charAt(0))) {
+                    active.insert(ke.string().charAt(0));
                 }
             } else {
-                active.insert(ke.character());
+                active.insert(ke.string().charAt(0));
             }
         }
     }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SearchHighlighter.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SearchHighlighter.java
index 878d81b5f249..49a90dadfc2e 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SearchHighlighter.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SearchHighlighter.java
@@ -248,14 +248,14 @@ class SearchHighlighter {
 
     void renderFooterHints(List<Span> spans) {
         if (findInputActive) {
-            spans.add(Span.styled(" /", HINT_KEY_STYLE));
+            spans.add(Span.styled(" /", Theme.hintKey()));
             spans.add(Span.raw(searchInputState.text() + "█  "));
             hint(spans, "Enter", "search");
             hintLast(spans, "Esc", "cancel");
             return;
         }
         if (highlightInputActive) {
-            spans.add(Span.styled(" h:", HINT_KEY_STYLE));
+            spans.add(Span.styled(" h:", Theme.hintKey()));
             spans.add(Span.raw(searchInputState.text() + "█  "));
             hint(spans, "Enter", "set");
             hintLast(spans, "Esc", "cancel");
@@ -271,7 +271,7 @@ class SearchHighlighter {
             String pos = findMatches.isEmpty()
                     ? "0/0"
                     : (findMatchIndex + 1) + "/" + findMatches.size();
-            spans.add(Span.styled("  /", HINT_KEY_STYLE));
+            spans.add(Span.styled("  /", Theme.hintKey()));
             spans.add(Span.raw("\"" + findTerm + "\" [" + pos + "]  "));
         }
     }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SendMessagePopup.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SendMessagePopup.java
index 82a3fba80f48..04661c6cfa39 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SendMessagePopup.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SendMessagePopup.java
@@ -346,7 +346,7 @@ class SendMessagePopup {
             return true;
         }
         if (ke.code() == KeyCode.CHAR) {
-            activeInput.insert(ke.character());
+            activeInput.insert(ke.string().charAt(0));
             return true;
         }
         return true;
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ShellPanel.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ShellPanel.java
index 8eea84b52b23..be40816284da 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ShellPanel.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ShellPanel.java
@@ -212,10 +212,11 @@ class ShellPanel {
 
         lastArea = area;
 
-        // Render border matching other tabs
+        // Focused pane: orange border + themed title (an open shell holds 
input focus)
         Block block = Block.builder()
                 .borderType(BorderType.ROUNDED).borders(Borders.ALL)
-                .title(Title.from(Line.from(Span.styled(" Shell ", 
Style.EMPTY.bold()))))
+                .borderStyle(Theme.borderFocused())
+                .title(Title.from(Line.from(Span.styled(" Shell ", 
Theme.title()))))
                 .build();
         frame.renderWidget(block, area);
         Rect inner = block.inner(area);
@@ -230,9 +231,9 @@ class ShellPanel {
 
         // Handle resize
         if (screenTerminal != null && (innerWidth != lastWidth || innerHeight 
!= lastHeight)) {
-            screenTerminal.setSize(innerWidth, innerHeight);
+            screenTerminal.setSize(Size.of(innerWidth, innerHeight));
             if (virtualTerminal != null) {
-                virtualTerminal.setSize(new Size(innerWidth, innerHeight));
+                virtualTerminal.setSize(Size.of(innerWidth, innerHeight));
             }
             lastWidth = innerWidth;
             lastHeight = innerHeight;
@@ -403,7 +404,7 @@ class ShellPanel {
             DelegateOutputStream delegateOut = new DelegateOutputStream();
             virtualTerminal = new LineDisciplineTerminal(
                     "tui-shell", "screen-256color", delegateOut, 
StandardCharsets.UTF_8);
-            virtualTerminal.setSize(new Size(width, height));
+            virtualTerminal.setSize(Size.of(width, height));
 
             // Feedback loop: VT100 responses go back as terminal input
             OutputStream feedbackOutput = new OutputStream() {
@@ -603,9 +604,10 @@ class ShellPanel {
 
     static byte[] encodeKeyEvent(KeyEvent ke) {
         if (ke.code() == KeyCode.CHAR) {
-            char ch = ke.character();
-            if (ke.hasCtrl()) {
+            String s = ke.string();
+            if (ke.hasCtrl() && s.length() == 1) {
                 // Ctrl+letter → control character
+                char ch = s.charAt(0);
                 if (ch >= 'a' && ch <= 'z') {
                     return new byte[] { (byte) (ch - 'a' + 1) };
                 }
@@ -613,7 +615,7 @@ class ShellPanel {
                     return new byte[] { (byte) (ch - 'A' + 1) };
                 }
             }
-            return Character.toString(ch).getBytes(StandardCharsets.UTF_8);
+            return s.getBytes(StandardCharsets.UTF_8);
         }
 
         return switch (ke.code()) {
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
new file mode 100644
index 000000000000..4391433df555
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/Theme.java
@@ -0,0 +1,309 @@
+/*
+ * 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.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import dev.tamboui.css.Styleable;
+import dev.tamboui.css.engine.StyleEngine;
+import dev.tamboui.style.Color;
+import dev.tamboui.style.Style;
+import org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
+import org.apache.camel.dsl.jbang.core.common.Printer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Central semantic theme for the Camel TUI. Call sites reference intent 
(accent, border, success, ...) so the concrete
+ * palette lives in one place. Values are resolved from CSS stylesheets 
({@code dark.tcss} / {@code light.tcss}) through
+ * a shared {@link StyleEngine}; the active stylesheet can be switched at 
runtime and is persisted to user config.
+ * <p/>
+ * Palette policy: the brand accent is Camel orange (truecolor), reserved for 
accent and borders. Status colors use ANSI
+ * names in dark mode (so they respect the terminal) and explicit darker hex 
in light mode. If a stylesheet is missing
+ * or malformed, the facade falls back to the built-in palette below and logs 
once, so a cosmetic failure never crashes
+ * the TUI.
+ */
+final class Theme {
+
+    /** Camel brand orange. */
+    static final Color ACCENT = Color.rgb(0xF6, 0x91, 0x23);
+
+    private static final Logger LOG = LoggerFactory.getLogger(Theme.class);
+
+    private static final String PROP_KEY = "camel.tui.theme";
+    private static final String DARK = "dark";
+    private static final String LIGHT = "light";
+
+    // Fallback palette mirrors the dark stylesheet, used when CSS is 
unavailable.
+    private static final Style FALLBACK_ACCENT_BG = 
Style.EMPTY.fg(Color.WHITE).bg(ACCENT).bold();
+    private static final Style FALLBACK_HINT_KEY = 
Style.EMPTY.fg(Color.BLACK).bg(ACCENT).bold();
+    private static final Style FALLBACK_BORDER = 
Style.EMPTY.fg(Color.DARK_GRAY);
+    private static final Style FALLBACK_BORDER_FOCUSED = 
Style.EMPTY.fg(ACCENT);
+    private static final Style FALLBACK_TITLE = Style.EMPTY.fg(ACCENT).bold();
+    private static final Style FALLBACK_SUCCESS = 
Style.EMPTY.fg(Color.LIGHT_GREEN);
+    private static final Style FALLBACK_WARNING = 
Style.EMPTY.fg(Color.LIGHT_YELLOW);
+    private static final Style FALLBACK_ERROR = 
Style.EMPTY.fg(Color.LIGHT_RED);
+    private static final Style FALLBACK_MUTED = Style.EMPTY.dim();
+    private static final Style FALLBACK_SELECTION = 
Style.EMPTY.fg(Color.WHITE).bold().onBlue();
+    private static final Style FALLBACK_INFO = Style.EMPTY.fg(Color.CYAN);
+    private static final Style FALLBACK_NOTICE = Style.EMPTY.fg(Color.MAGENTA);
+    private static final Style FALLBACK_MCP_ACTIVE = 
Style.EMPTY.fg(Color.LIGHT_GREEN);
+    private static final Style FALLBACK_MCP_IDLE = 
Style.EMPTY.fg(Color.DARK_GRAY);
+    private static final Style FALLBACK_MCP_DOWN = 
Style.EMPTY.fg(Color.LIGHT_RED);
+    private static final Color FALLBACK_ZEBRA = Color.rgb(0x1C, 0x1C, 0x1C);
+
+    private static final Map<String, Style> CACHE = new HashMap<>();
+
+    private static boolean initialized;
+    private static boolean fallbackLogged;
+    private static StyleEngine engine;
+    private static String mode = DARK;
+
+    private Theme() {
+    }
+
+    static Color accent() {
+        StyleEngine e = engine();
+        if (e == null) {
+            return ACCENT;
+        }
+        try {
+            return e.resolve(new Token("accent")).foreground().orElse(ACCENT);
+        } catch (RuntimeException ex) {
+            logFallbackOnce(ex);
+            return ACCENT;
+        }
+    }
+
+    /**
+     * Subtle alternating-row background for zebra striping. Theme-aware (dark 
gray on dark, light gray on light) so
+     * stripes stay readable on both terminals. Applied at the row level so it 
never overrides the selection highlight.
+     */
+    static Color zebra() {
+        StyleEngine e = engine();
+        if (e == null) {
+            return FALLBACK_ZEBRA;
+        }
+        try {
+            return e.resolve(new 
Token("row-alt")).background().orElse(FALLBACK_ZEBRA);
+        } catch (RuntimeException ex) {
+            logFallbackOnce(ex);
+            return FALLBACK_ZEBRA;
+        }
+    }
+
+    /** White-on-orange: active tab highlight. */
+    static Style accentBg() {
+        return style("accent-bg", FALLBACK_ACCENT_BG);
+    }
+
+    /** Black-on-orange chip: key hints in footers and prompts. */
+    static Style hintKey() {
+        return style("hint-key", FALLBACK_HINT_KEY);
+    }
+
+    /** Dim border for unfocused panels. */
+    static Style border() {
+        return style("border", FALLBACK_BORDER);
+    }
+
+    /** Orange border for the focused panel. */
+    static Style borderFocused() {
+        return style("border-focused", FALLBACK_BORDER_FOCUSED);
+    }
+
+    /** Panel and border titles. */
+    static Style title() {
+        return style("title", FALLBACK_TITLE);
+    }
+
+    static Style success() {
+        return style("success", FALLBACK_SUCCESS);
+    }
+
+    static Style warning() {
+        return style("warning", FALLBACK_WARNING);
+    }
+
+    static Style error() {
+        return style("error", FALLBACK_ERROR);
+    }
+
+    static Style muted() {
+        return style("muted", FALLBACK_MUTED);
+    }
+
+    /** Row/selection highlight (matches the existing list highlight). */
+    static Style selectionBg() {
+        return style("selection", FALLBACK_SELECTION);
+    }
+
+    /** Informational accent (header integration count). */
+    static Style info() {
+        return style("info", FALLBACK_INFO);
+    }
+
+    /** Secondary accent (header infra / selected). */
+    static Style notice() {
+        return style("notice", FALLBACK_NOTICE);
+    }
+
+    /** MCP indicator: connected with recent activity. */
+    static Style mcpActive() {
+        return style("mcp-active", FALLBACK_MCP_ACTIVE);
+    }
+
+    /** MCP indicator: connected but idle. */
+    static Style mcpIdle() {
+        return style("mcp-idle", FALLBACK_MCP_IDLE);
+    }
+
+    /** MCP indicator: not connected. */
+    static Style mcpDown() {
+        return style("mcp-down", FALLBACK_MCP_DOWN);
+    }
+
+    /** The active theme mode: {@code "dark"} or {@code "light"}. */
+    static String mode() {
+        engine();
+        return mode;
+    }
+
+    /** Flip the active theme, clear the cache, persist the new value, and 
return the new mode. */
+    static synchronized String toggle() {
+        String next = DARK.equals(mode) ? LIGHT : DARK;
+        setMode(next);
+        persist(next);
+        return next;
+    }
+
+    /** Activate a specific mode without persisting. Unknown values fall back 
to dark. */
+    static synchronized void setMode(String newMode) {
+        engine();
+        String resolved = DARK.equals(newMode) || LIGHT.equals(newMode) ? 
newMode : DARK;
+        if (engine != null) {
+            try {
+                engine.setActiveStylesheet(resolved);
+            } catch (RuntimeException ex) {
+                logFallbackOnce(ex);
+            }
+        }
+        mode = resolved;
+        CACHE.clear();
+    }
+
+    /** Test hook: drop all process-wide state so the next access 
reinitializes from config. */
+    static synchronized void resetForTesting() {
+        initialized = false;
+        fallbackLogged = false;
+        engine = null;
+        mode = DARK;
+        CACHE.clear();
+    }
+
+    private static synchronized Style style(String id, Style fallback) {
+        StyleEngine e = engine();
+        if (e == null) {
+            return fallback;
+        }
+        return CACHE.computeIfAbsent(id, key -> {
+            try {
+                return e.resolve(new Token(key)).toStyle();
+            } catch (RuntimeException ex) {
+                logFallbackOnce(ex);
+                return fallback;
+            }
+        });
+    }
+
+    private static synchronized StyleEngine engine() {
+        if (initialized) {
+            return engine;
+        }
+        initialized = true;
+        mode = loadPersistedMode();
+        try {
+            StyleEngine e = StyleEngine.create();
+            e.loadStylesheet(DARK, "tui/themes/dark.tcss");
+            e.loadStylesheet(LIGHT, "tui/themes/light.tcss");
+            e.setActiveStylesheet(mode);
+            engine = e;
+        } catch (Exception ex) {
+            engine = null;
+            logFallbackOnce(ex);
+        }
+        return engine;
+    }
+
+    private static String loadPersistedMode() {
+        String[] holder = { DARK };
+        try {
+            CommandLineHelper.loadProperties(props -> {
+                String v = props.getProperty(PROP_KEY);
+                if (DARK.equals(v) || LIGHT.equals(v)) {
+                    holder[0] = v;
+                }
+            });
+        } catch (RuntimeException ex) {
+            // Config unreadable; keep the default mode.
+        }
+        return holder[0];
+    }
+
+    private static void persist(String newMode) {
+        try {
+            CommandLineHelper.createPropertyFile(false);
+            CommandLineHelper.loadProperties(props -> {
+                props.setProperty(PROP_KEY, newMode);
+                CommandLineHelper.storeProperties(props,
+                        new Printer.QuietPrinter(new 
Printer.SystemOutPrinter()), false);
+            });
+        } catch (Exception ex) {
+            logFallbackOnce(ex);
+        }
+    }
+
+    private static void logFallbackOnce(Throwable t) {
+        if (!fallbackLogged) {
+            fallbackLogged = true;
+            LOG.warn("Camel TUI theme stylesheet unavailable; using built-in 
palette", t);
+        }
+    }
+
+    /** Minimal synthetic {@link Styleable}: an id-only token used for engine 
resolution. */
+    private record Token(String id) implements Styleable {
+
+        @Override
+        public Optional<String> cssId() {
+            return Optional.of(id);
+        }
+
+        @Override
+        public Set<String> cssClasses() {
+            return Collections.emptySet();
+        }
+
+        @Override
+        public Optional<Styleable> cssParent() {
+            return Optional.empty();
+        }
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/dark.tcss
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/dark.tcss
new file mode 100644
index 000000000000..41c141bc8752
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/dark.tcss
@@ -0,0 +1,37 @@
+/*
+ * 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 dark theme (default). Single source of color truth for Theme 
tokens. */
+
+$brand: #F69123;
+
+#accent         { color: $brand; }
+#accent-bg      { color: white; background: $brand; text-style: bold; }
+#hint-key       { color: black; background: $brand; text-style: bold; }
+#border         { color: dark-gray; }
+#border-focused { color: $brand; }
+#title          { color: $brand; text-style: bold; }
+#success        { color: light-green; }
+#warning        { color: light-yellow; }
+#error          { color: light-red; }
+#muted          { text-style: dim; }
+#selection      { color: white; background: blue; text-style: bold; }
+#info           { color: cyan; }
+#notice         { color: magenta; }
+#row-alt        { background: #1C1C1C; }
+#mcp-active     { color: light-green; }
+#mcp-idle       { color: dark-gray; }
+#mcp-down       { color: light-red; }
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
new file mode 100644
index 000000000000..de7dee5b7a93
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/light.tcss
@@ -0,0 +1,37 @@
+/*
+ * 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 light theme. Brand orange stays truecolor; status hues darkened 
for light terminals. */
+
+$brand: #F69123;
+
+#accent         { color: $brand; }
+#accent-bg      { color: white; background: $brand; text-style: bold; }
+#hint-key       { color: black; background: $brand; text-style: bold; }
+#border         { color: #888888; }
+#border-focused { color: $brand; }
+#title          { color: $brand; text-style: bold; }
+#success        { color: #007700; }
+#warning        { color: #996600; }
+#error          { color: #cc0000; }
+#muted          { color: #666666; }
+#selection      { color: white; background: blue; text-style: bold; }
+#info           { color: #006688; }
+#notice         { color: #884488; }
+#row-alt        { background: #EBEBEB; }
+#mcp-active     { color: #007700; }
+#mcp-idle       { color: #888888; }
+#mcp-down       { color: #cc0000; }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ShellPanelColorTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ShellPanelColorTest.java
index 586fc5d36351..f522ba30300d 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ShellPanelColorTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ShellPanelColorTest.java
@@ -26,7 +26,7 @@ import static org.junit.jupiter.api.Assertions.assertNull;
 class ShellPanelColorTest {
 
     // ScreenTerminal stores colors as the top nibble of each channel of its 
xterm palette. The 16 standard
-    // ANSI colors must be recognised and mapped to terminal-themed colors, 
otherwise (for example) ANSI red
+    // ANSI colors must be recognized and mapped to terminal-themed colors, 
otherwise (for example) ANSI red
     // is reconstructed as a literal RGB(136,0,0) that is unreadable on dark 
backgrounds.
 
     @Test
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SyntaxHighlighterTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SyntaxHighlighterTest.java
index d060b58fdf27..871fffd6b7db 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SyntaxHighlighterTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SyntaxHighlighterTest.java
@@ -22,6 +22,7 @@ import dev.tamboui.text.Span;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
 
 class SyntaxHighlighterTest {
 
@@ -77,7 +78,7 @@ class SyntaxHighlighterTest {
     void preservesLeadingIndentationUnstyled() {
         Line line = SyntaxHighlighter.highlightLine("  camel.x=1", 
SyntaxHighlighter.Language.PROPERTIES);
         // the indentation is emitted as a raw (unstyled) span
-        assertEquals(null, fg(line, "  "));
+        assertNull(fg(line, "  "));
         assertEquals(SyntaxHighlighter.MONOKAI_KEYWORD, fg(line, "camel.x"));
         assertEquals(SyntaxHighlighter.MONOKAI_STRING, fg(line, "1"));
         assertRoundTrip(line, "  camel.x=1");
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
new file mode 100644
index 000000000000..b9be5c9240b9
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ThemeTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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.nio.file.Path;
+
+import dev.tamboui.style.Color;
+import dev.tamboui.style.Style;
+import org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+class ThemeTest {
+
+    @TempDir
+    Path home;
+
+    @BeforeEach
+    void setUp() {
+        // Isolate user config per test so persistence never touches the real 
home dir.
+        CommandLineHelper.useHomeDir(home.toString());
+        Theme.resetForTesting();
+    }
+
+    @AfterEach
+    void tearDown() {
+        // Reset the process-wide engine so toggles do not leak across tests.
+        Theme.resetForTesting();
+    }
+
+    @Test
+    void accentIsBrandOrangeInDefaultTheme() {
+        // Guards palette drift: the brand accent must stay Camel orange 
#F69123 and load from CSS.
+        Theme.setMode("dark");
+        assertEquals(Color.rgb(0xF6, 0x91, 0x23), Theme.accent());
+        assertEquals(Color.rgb(0xF6, 0x91, 0x23), Theme.ACCENT);
+    }
+
+    @Test
+    void darkPaletteResolvesExpectedTokens() {
+        // Asserts the dark stylesheet actually loads and each token resolves 
to the intended style,
+        // not merely that the accessor exists.
+        Theme.setMode("dark");
+        assertEquals(Style.EMPTY.fg(Color.WHITE).bg(Theme.ACCENT).bold(), 
Theme.accentBg());
+        assertEquals(Style.EMPTY.fg(Color.BLACK).bg(Theme.ACCENT).bold(), 
Theme.hintKey());
+        assertEquals(Style.EMPTY.fg(Color.DARK_GRAY), Theme.border());
+        assertEquals(Style.EMPTY.fg(Theme.ACCENT), Theme.borderFocused());
+        assertEquals(Style.EMPTY.fg(Theme.ACCENT).bold(), Theme.title());
+        assertEquals(Style.EMPTY.fg(Color.LIGHT_GREEN), Theme.success());
+        assertEquals(Style.EMPTY.fg(Color.LIGHT_YELLOW), Theme.warning());
+        assertEquals(Style.EMPTY.fg(Color.LIGHT_RED), Theme.error());
+        assertEquals(Style.EMPTY.dim(), Theme.muted());
+        assertEquals(Style.EMPTY.fg(Color.WHITE).bold().onBlue(), 
Theme.selectionBg());
+        assertEquals(Style.EMPTY.fg(Color.CYAN), Theme.info());
+        assertEquals(Style.EMPTY.fg(Color.MAGENTA), Theme.notice());
+        assertEquals(Style.EMPTY.fg(Color.LIGHT_GREEN), Theme.mcpActive());
+        assertEquals(Style.EMPTY.fg(Color.DARK_GRAY), Theme.mcpIdle());
+        assertEquals(Style.EMPTY.fg(Color.LIGHT_RED), Theme.mcpDown());
+        assertEquals(Color.rgb(0x1C, 0x1C, 0x1C), Theme.zebra());
+    }
+
+    @Test
+    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(Style.EMPTY.fg(Color.rgb(0x00, 0x77, 0x00)), 
Theme.success());
+        assertEquals(Style.EMPTY.fg(Color.rgb(0x88, 0x88, 0x88)), 
Theme.border());
+        // MCP indicator hues track the light palette: idle gray and down red 
differ from the dark ANSI variants.
+        assertEquals(Style.EMPTY.fg(Color.rgb(0x00, 0x77, 0x00)), 
Theme.mcpActive());
+        assertEquals(Style.EMPTY.fg(Color.rgb(0x88, 0x88, 0x88)), 
Theme.mcpIdle());
+        assertEquals(Style.EMPTY.fg(Color.rgb(0xcc, 0x00, 0x00)), 
Theme.mcpDown());
+        // Zebra background is theme-aware: light gray on light, unlike the 
dark gray used on dark.
+        assertEquals(Color.rgb(0xEB, 0xEB, 0xEB), Theme.zebra());
+    }
+
+    @Test
+    void toggleFlipsModeAndChangesThemeDependentToken() {
+        Theme.setMode("dark");
+        assertEquals("dark", Theme.mode());
+        Style darkBorder = Theme.border();
+
+        String newMode = Theme.toggle();
+
+        assertEquals("light", newMode);
+        assertEquals("light", Theme.mode());
+        assertNotEquals(darkBorder, Theme.border());
+    }
+
+    @Test
+    void togglePersistsAndAFreshLoadActivatesIt() {
+        Theme.setMode("dark");
+        Theme.toggle(); // -> light, persisted to camel.tui.theme
+
+        String[] stored = { null };
+        CommandLineHelper.loadProperties(p -> stored[0] = 
p.getProperty("camel.tui.theme"));
+        assertEquals("light", stored[0]);
+
+        // A fresh Theme load (statics reset) must read back the persisted 
mode.
+        Theme.resetForTesting();
+        assertEquals("light", Theme.mode());
+    }
+
+    @Test
+    void accessorsNeverReturnNull() {
+        // Resilience: even when resolution is exercised the palette is always 
usable.
+        Theme.setMode("dark");
+        assertNotNull(Theme.accentBg());
+        assertNotNull(Theme.hintKey());
+        assertNotNull(Theme.border());
+        assertNotNull(Theme.borderFocused());
+        assertNotNull(Theme.title());
+        assertNotNull(Theme.success());
+        assertNotNull(Theme.warning());
+        assertNotNull(Theme.error());
+        assertNotNull(Theme.muted());
+        assertNotNull(Theme.selectionBg());
+        assertNotNull(Theme.info());
+        assertNotNull(Theme.notice());
+        assertNotNull(Theme.mcpActive());
+        assertNotNull(Theme.mcpIdle());
+        assertNotNull(Theme.mcpDown());
+        assertNotNull(Theme.zebra());
+    }
+}
diff --git a/pom.xml b/pom.xml
index ceaac32c4578..3c13582af509 100644
--- a/pom.xml
+++ b/pom.xml
@@ -441,6 +441,7 @@
                             
<spring.schemas>CAMEL_PROPERTIES_STYLE</spring.schemas>
                             <sql>DOUBLEDASHES_STYLE</sql>
                             <tape>SCRIPT_STYLE</tape>
+                            <tcss>SLASHSTAR_STYLE</tcss>
                             <thrift>JAVADOC_STYLE</thrift>
                             <toml>SCRIPT_STYLE</toml>
                             <unrealircd.conf>SLASHSTAR_STYLE</unrealircd.conf>

Reply via email to