This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch fix/CAMEL-23934
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 59e5e93dd3e34d2b339f9f857043194a7ca6db88
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Jul 8 13:34:45 2026 +0200

    CAMEL-23934: Add theme token validation and theme reference doc
    
    Validate all 27 CSS tokens on stylesheet load so missing tokens
    fail fast instead of silently falling back. Replace remaining
    hardcoded Color.rgb brand orange with Theme.accent() in
    CamelCatalogTui, CamelMonitor, HttpTab, and SourceViewer.
    Add theme.md reference documenting all tokens for theme authors.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../jbang/core/commands/tui/CamelCatalogTui.java   |  7 +-
 .../dsl/jbang/core/commands/tui/CamelMonitor.java  |  6 +-
 .../camel/dsl/jbang/core/commands/tui/HttpTab.java |  3 +-
 .../dsl/jbang/core/commands/tui/SourceViewer.java  |  3 +-
 .../dsl/jbang/core/commands/tui/SqlQueryTab.java   |  3 +-
 .../camel/dsl/jbang/core/commands/tui/Theme.java   | 30 +++++++
 .../src/main/resources/tui/themes/theme.md         | 98 ++++++++++++++++++++++
 7 files changed, 137 insertions(+), 13 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelCatalogTui.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelCatalogTui.java
index c90e8e7e46ab..0f736f5e64ce 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelCatalogTui.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelCatalogTui.java
@@ -24,7 +24,6 @@ import java.util.Map;
 import dev.tamboui.layout.Constraint;
 import dev.tamboui.layout.Layout;
 import dev.tamboui.layout.Rect;
-import dev.tamboui.style.Color;
 import dev.tamboui.style.Overflow;
 import dev.tamboui.style.Style;
 import dev.tamboui.terminal.Frame;
@@ -411,7 +410,7 @@ public class CamelCatalogTui extends CamelCommand {
 
     private void renderHeader(Frame frame, Rect area) {
         Line titleLine = Line.from(
-                Span.styled(" Camel Catalog", Style.EMPTY.fg(Color.rgb(0xF6, 
0x91, 0x23)).bold()),
+                Span.styled(" Camel Catalog", 
Style.EMPTY.fg(Theme.accent()).bold()),
                 Span.raw("  "),
                 Span.styled(filteredComponents.size() + "/" + 
allComponents.size() + " components",
                         Style.EMPTY.fg(Theme.accent())));
@@ -454,7 +453,7 @@ public class CamelCatalogTui extends CamelCommand {
         }
 
         Style borderStyle = focus == FOCUS_LIST
-                ? Style.EMPTY.fg(Color.rgb(0xF6, 0x91, 0x23))
+                ? Style.EMPTY.fg(Theme.accent())
                 : Style.EMPTY;
 
         String modePrefix = componentFullText ? "/" : "";
@@ -479,7 +478,7 @@ public class CamelCatalogTui extends CamelCommand {
 
     private void renderOptionsTable(Frame frame, Rect area) {
         Style borderStyle = focus == FOCUS_OPTIONS
-                ? Style.EMPTY.fg(Color.rgb(0xF6, 0x91, 0x23))
+                ? Style.EMPTY.fg(Theme.accent())
                 : Style.EMPTY;
 
         String optModePrefix = optionFullText ? "/" : "";
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 ec6160ea73a9..eb883ae17d27 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
@@ -1190,8 +1190,8 @@ public class CamelMonitor extends CamelCommand {
                         Paragraph.builder().text(Text.from(titleLine)).build(),
                         leftArea);
                 Color waveColor = activeNotificationError
-                        ? Theme.error().fg().orElse(Color.RED)
-                        : Theme.success().fg().orElse(Color.GREEN);
+                        ? Theme.error().fg().orElseThrow()
+                        : Theme.success().fg().orElseThrow();
                 WaveText wave = WaveText.builder()
                         .text(activeNotification)
                         .color(waveColor)
@@ -1214,7 +1214,7 @@ public class CamelMonitor extends CamelCommand {
     }
 
     private void renderTooSmall(Frame frame, Rect area) {
-        Style orange = Style.EMPTY.fg(Color.rgb(0xF6, 0x91, 0x23));
+        Style orange = Style.EMPTY.fg(Theme.accent());
         Style normal = Style.EMPTY;
         Style bold = Style.EMPTY.bold();
 
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 5522f02172c3..b63e19d0ea1b 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
@@ -35,7 +35,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
 import dev.tamboui.layout.Constraint;
 import dev.tamboui.layout.Layout;
 import dev.tamboui.layout.Rect;
-import dev.tamboui.style.Color;
 import dev.tamboui.style.Style;
 import dev.tamboui.terminal.Frame;
 import dev.tamboui.text.Line;
@@ -1217,7 +1216,7 @@ class HttpTab extends AbstractTableTab {
             case "POST" -> Theme.label();
             case "PUT" -> Style.EMPTY.fg(Theme.accent());
             case "DELETE" -> Theme.error();
-            case "PATCH" -> Style.EMPTY.fg(Color.rgb(0xFF, 0x80, 0x00));
+            case "PATCH" -> Theme.warning();
             default -> Style.EMPTY.dim();
         };
     }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceViewer.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceViewer.java
index 8a021994117e..ae8629819280 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceViewer.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceViewer.java
@@ -28,7 +28,6 @@ import java.util.function.IntConsumer;
 import dev.tamboui.layout.Constraint;
 import dev.tamboui.layout.Layout;
 import dev.tamboui.layout.Rect;
-import dev.tamboui.style.Color;
 import dev.tamboui.style.Overflow;
 import dev.tamboui.style.Style;
 import dev.tamboui.terminal.Frame;
@@ -777,7 +776,7 @@ class SourceViewer {
         Line highlighted = SyntaxHighlighter.highlightLine(code, language);
 
         List<Span> spans = new ArrayList<>();
-        Style selBg = Style.EMPTY.bg(Color.rgb(30, 45, 70));
+        Style selBg = Theme.selectionBg();
         if (isSelected) {
             spans.add(Span.styled(">> ", Theme.label().bold()));
             if (!prefix.isEmpty()) {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SqlQueryTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SqlQueryTab.java
index fe7c138fc03a..d18f28f61cc4 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SqlQueryTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SqlQueryTab.java
@@ -24,7 +24,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
 import dev.tamboui.layout.Constraint;
 import dev.tamboui.layout.Layout;
 import dev.tamboui.layout.Rect;
-import dev.tamboui.style.Color;
 import dev.tamboui.style.Style;
 import dev.tamboui.terminal.Frame;
 import dev.tamboui.text.Line;
@@ -796,7 +795,7 @@ class SqlQueryTab extends AbstractTab {
             } else if (isFocused) {
                 boolean changed = 
!editInputs[i].text().equals(editOriginalValues[i]);
                 Style cursorStyle = changed
-                        ? 
Style.EMPTY.reversed().fg(Theme.success().fg().orElse(Color.GREEN))
+                        ? 
Style.EMPTY.reversed().fg(Theme.success().fg().orElseThrow())
                         : Style.EMPTY.reversed();
                 TextInput input = TextInput.builder()
                         .cursorStyle(cursorStyle)
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 366b0becd796..08fbe9d892a9 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
@@ -19,8 +19,10 @@ package org.apache.camel.dsl.jbang.core.commands.tui;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
@@ -85,6 +87,15 @@ public final class Theme {
     private static final Color FALLBACK_DIAGRAM_EIP = Color.rgb(0x89, 0x57, 
0xE5);
     private static final Color FALLBACK_DIAGRAM_DEFAULT = Color.rgb(0x80, 
0x80, 0x80);
 
+    private static final String[] REQUIRED_TOKENS = {
+            "accent", "accent-bg", "hint-key", "border", "border-focused", 
"title",
+            "success", "warning", "error", "muted", "selection", "info", 
"notice",
+            "row-alt", "base-bg", "base-fg",
+            "label", "change", "search-match",
+            "diagram-border", "diagram-id", "diagram-from", "diagram-to",
+            "diagram-choice", "diagram-action", "diagram-eip", 
"diagram-default"
+    };
+
     private static final Map<String, Style> CACHE = new HashMap<>();
 
     private static boolean initialized;
@@ -414,6 +425,7 @@ public final class Theme {
             String cssContent = loadCssResource(mode.stylesheetResource());
             e.addStylesheet(stylesheet, cssContent);
             e.setActiveStylesheet(stylesheet);
+            validateTokens(e, stylesheet);
             engine = e;
         } catch (Exception ex) {
             engine = null;
@@ -504,6 +516,24 @@ public final class Theme {
         }
     }
 
+    private static void validateTokens(StyleEngine e, String stylesheet) {
+        List<String> missing = new ArrayList<>();
+        for (String token : REQUIRED_TOKENS) {
+            try {
+                Style resolved = e.resolve(new Token(token)).toStyle();
+                if (resolved.equals(Style.EMPTY)) {
+                    missing.add(token);
+                }
+            } catch (RuntimeException ex) {
+                missing.add(token);
+            }
+        }
+        if (!missing.isEmpty()) {
+            throw new IllegalStateException(
+                    "Theme stylesheet '" + stylesheet + "' is missing required 
CSS tokens: " + missing);
+        }
+    }
+
     /** Minimal synthetic {@link Styleable}: an id-only token used for engine 
resolution. */
     private record Token(String id) implements Styleable {
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/theme.md 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/theme.md
new file mode 100644
index 000000000000..ce3a4479c4a0
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/theme.md
@@ -0,0 +1,98 @@
+# Camel TUI Theme Reference
+
+This document describes the CSS token system used by the Camel TUI.
+Every `.tcss` theme file must define all 27 tokens listed below.
+Missing tokens cause a startup validation error.
+
+## File Format
+
+Theme files use the `.tcss` (TamboUI CSS) format with standard CSS syntax.
+Variables are declared with `$name: value;` and referenced as `$name`.
+
+```css
+$brand: #F69123;
+#accent { color: $brand; }
+#success { color: #4EC9B0; }
+#selection { color: white; background: #264F78; text-style: bold; }
+```
+
+### Supported properties
+
+| Property | Values |
+|----------|--------|
+| `color` | Hex (`#RRGGBB`), named (`white`, `black`), or variable (`$name`) |
+| `background` | Same as `color` |
+| `text-style` | `bold`, `dim`, `italic`, `underline`, `reversed` |
+
+## Token Reference
+
+### Brand & Chrome (10 tokens)
+
+| Token | Type | Purpose |
+|-------|------|---------|
+| `accent` | fg | Brand color for focused borders, links, and highlights |
+| `accent-bg` | fg+bg+bold | Inverted brand badge (e.g., active tab) |
+| `hint-key` | fg+bg+bold | Key-hint chips in footers and prompts |
+| `border` | fg | Unfocused panel borders |
+| `border-focused` | fg | Focused panel borders |
+| `title` | fg+bold | Panel and border titles |
+| `selection` | fg+bg+bold | Row/item selection highlight |
+| `row-alt` | bg | Zebra-stripe background for alternating rows |
+| `base-bg` | bg | Main content area background |
+| `base-fg` | fg | Default text foreground |
+
+### Semantic Status (6 tokens)
+
+| Token | Type | Purpose |
+|-------|------|---------|
+| `success` | fg | OK, running, passed, healthy |
+| `warning` | fg | Caution, WARN log level, thresholds |
+| `error` | fg | Failed, errors, stopped, ERROR log level |
+| `muted` | fg | Disabled, secondary, placeholder text |
+| `info` | fg | Informational accent (counts, prompts) |
+| `notice` | fg | Secondary accent, typically purple (e.g., TRACE log level) |
+
+### Content (3 tokens)
+
+| Token | Type | Purpose |
+|-------|------|---------|
+| `label` | fg | Field labels, section headers, key hints, table headers |
+| `change` | fg | Changed-value indicator in trace diffs |
+| `search-match` | fg+bg | Search/find match highlight |
+
+### Diagram (8 tokens)
+
+| Token | Type | Purpose |
+|-------|------|---------|
+| `diagram-border` | fg | Box-drawing borders in route diagrams |
+| `diagram-id` | fg | Route ID text |
+| `diagram-from` | fg | "from" EIP nodes |
+| `diagram-to` | fg | "to", "enrich", "marshal", "transform" nodes |
+| `diagram-choice` | fg | "choice", "when", "otherwise" nodes |
+| `diagram-action` | fg | "bean", "process", "log", "script" nodes |
+| `diagram-eip` | fg | Routing EIPs (split, aggregate, multicast, etc.) |
+| `diagram-default` | fg | Fallback for unknown EIP types |
+
+## Design Guidelines
+
+When creating a new theme:
+
+- **Contrast**: ensure all `fg` tokens are readable against `base-bg`.
+  Status colors (`success`, `error`, `warning`) must be distinguishable
+  from each other and from `muted`.
+- **Brand**: `accent` is Camel orange (`#F69123`) by convention but can
+  be changed. `accent-bg` and `hint-key` should use the same brand color
+  as background with a contrasting foreground (white or black).
+- **Selection**: `selection` needs high contrast since it overlays any
+  row content. Use a bold, distinct background.
+- **Diagram colors**: the 7 EIP colors should be visually distinct from
+  each other. They appear as foreground text on `base-bg`.
+- **Zebra striping**: `row-alt` background should be subtle, just enough
+  to distinguish alternating rows without clashing with `selection`.
+
+## Built-in Themes
+
+| Theme | File | Description |
+|-------|------|-------------|
+| `dark` | `dark.tcss` | VS Code-inspired dark palette (default) |
+| `light` | `light.tcss` | GitHub-inspired light palette |

Reply via email to