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

davsclaus pushed a commit to branch feature/CAMEL-24759-tamboui-05-cleanup
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 1bc4b8e6fa1eb4699b7894baea0a5f19b28dd47d
Author: Claus Ibsen <[email protected]>
AuthorDate: Sat Sep 19 10:23:37 2026 +0200

    CAMEL-24759: camel-jbang - TUI: drop the workarounds that TamboUI 0.5.0 
makes redundant
    
    TuiBackendHelper no longer wraps the explicit JLine backend for
    recording itself: TuiRunner.create does that for explicitly configured
    backends since tamboui/tamboui#418. The recording test now goes through
    createTuiRunner and asserts on the runner's backend instead.
    
    The throughput chart keeps its rate * 100 history intact and converts
    the y-axis label back to msg/s through DualSparkline.yAxisFormatter
    (tamboui/tamboui#396) rather than dividing the data before rendering,
    so sub-1 msg/s rates keep their bar height again. The payload size
    chart gets a compact byte formatter instead of the default 999+ cap.
    
    The plain-emoji icon rule from CAMEL-23818 stays: TamboUI 0.5.0 now
    measures base + VS16 sequences as 2 columns (tamboui/tamboui#388), but
    the xterm.js Unicode 11 width tables used by --web still treat VS16 as
    zero-width, so the comments now give that as the reason.
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../dsl/jbang/core/commands/tui/FlowHelper.java    | 30 ++++++++++--
 .../jbang/core/commands/tui/TuiBackendHelper.java  | 26 ++--------
 .../dsl/jbang/core/commands/tui/TuiIcons.java      |  8 +--
 .../jbang/core/commands/tui/FlowHelperTest.java    | 57 ++++++++++++++++++++++
 .../tui/TuiBackendHelperRecordingTest.java         | 41 +++++++++-------
 .../dsl/jbang/core/commands/tui/TuiIconsTest.java  |  7 +--
 6 files changed, 120 insertions(+), 49 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FlowHelper.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FlowHelper.java
index fb3a9c1b08cf..91064f51d6e4 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FlowHelper.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FlowHelper.java
@@ -126,11 +126,6 @@ final class FlowHelper {
         }
         long curIn = inArr[renderPoints - 1];
         long curOut = outArr[renderPoints - 1];
-        // scale down from internal precision (rate * THROUGHPUT_SCALE) to 
actual msg/s for y-axis
-        for (int i = 0; i < renderPoints; i++) {
-            inArr[i] = Math.round((double) inArr[i] / 
MetricsCollector.THROUGHPUT_SCALE);
-            outArr[i] = Math.round((double) outArr[i] / 
MetricsCollector.THROUGHPUT_SCALE);
-        }
 
         List<Span> titleSpans = new ArrayList<>();
         if (chartLabel != null) {
@@ -156,6 +151,9 @@ final class FlowHelper {
                 .topStyle(Theme.success())
                 .bottomStyle(Style.EMPTY.fg(Theme.accent()))
                 .showYAxis(true)
+                // the data stays scaled by THROUGHPUT_SCALE so sub-1 msg/s 
rates keep their bar height; the axis
+                // label converts back to msg/s like the title does
+                .yAxisFormatter(MetricsCollector::formatThroughput)
                 .xLabels("-" + renderPoints + "s", "-" + (renderPoints * 3 / 
4) + "s",
                         "-" + (renderPoints / 2) + "s", "-" + (renderPoints / 
4) + "s", "now")
                 
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
@@ -198,6 +196,7 @@ final class FlowHelper {
                 .topStyle(Theme.label())
                 .bottomStyle(Theme.notice())
                 .showYAxis(true)
+                .yAxisFormatter(FlowHelper::compactSize)
                 .xLabels("-" + renderPoints + "s", "-" + (renderPoints * 3 / 
4) + "s",
                         "-" + (renderPoints / 2) + "s", "-" + (renderPoints / 
4) + "s", "now")
                 
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
@@ -221,6 +220,27 @@ final class FlowHelper {
         }
     }
 
+    /**
+     * Formats a byte count into at most four characters for the sparkline 
y-axis, where {@link #sizeToString(long)}
+     * would not fit: {@code 512}, {@code 1.5K}, {@code 12K}, {@code 1.2M}.
+     */
+    static String compactSize(long size) {
+        if (size < 1000) {
+            return String.valueOf(Math.max(0, size));
+        }
+        double kb = size / 1024.0;
+        if (kb < 10) {
+            return String.format(Locale.US, "%.1fK", kb);
+        } else if (kb < 999.5) {
+            return Math.round(kb) + "K";
+        }
+        double mb = kb / 1024.0;
+        if (mb < 10) {
+            return String.format(Locale.US, "%.1fM", mb);
+        }
+        return Math.round(mb) + "M";
+    }
+
     private static long unbox(Long value) {
         return value != null ? value : 0L;
     }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiBackendHelper.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiBackendHelper.java
index 2b5fa294e30f..3a5791ac6e19 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiBackendHelper.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiBackendHelper.java
@@ -17,8 +17,6 @@
 package org.apache.camel.dsl.jbang.core.commands.tui;
 
 import dev.tamboui.backend.jline3.JLineBackend;
-import dev.tamboui.internal.record.RecordingBackend;
-import dev.tamboui.internal.record.RecordingConfig;
 import dev.tamboui.terminal.Backend;
 import dev.tamboui.tui.TuiConfig;
 import dev.tamboui.tui.TuiRunner;
@@ -40,26 +38,12 @@ final class TuiBackendHelper {
         return createTuiRunner(backend);
     }
 
-    static TuiRunner createTuiRunner(Backend backend) throws Exception {
-        return TuiRunner.create(
-                
TuiConfig.builder().backend(applyRecording(backend)).mouseCapture(true).bracketedPaste(true).build());
-    }
-
     /**
-     * Wraps the backend for Asciinema recording when {@code --record} 
configured the {@code tamboui.record*} system
-     * properties.
-     * <p>
-     * TamboUI normally does this inside {@code BackendFactory.create()}, but 
{@code TuiRunner} only calls that factory
-     * when no explicit backend is configured. Because we must pass an 
explicit backend (see
-     * {@link #createTuiRunner()}), the wrapping has to be done here instead, 
otherwise {@code --record} exits cleanly
-     * without replaying the tape or writing a {@code .cast} file.
+     * Creates the runner for an explicitly built backend. {@code 
TuiRunner.create} wraps it for Asciinema recording
+     * itself when {@code --record} configured the {@code tamboui.record*} 
system properties.
      */
-    static Backend applyRecording(Backend backend) {
-        // Guard on isEnabled() first: load() caches its result process-wide 
and installs a System.out capture
-        if (!RecordingConfig.isEnabled()) {
-            return backend;
-        }
-        RecordingConfig config = RecordingConfig.load();
-        return config != null ? new RecordingBackend(backend, config) : 
backend;
+    static TuiRunner createTuiRunner(Backend backend) throws Exception {
+        return TuiRunner.create(
+                
TuiConfig.builder().backend(backend).mouseCapture(true).bracketedPaste(true).build());
     }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiIcons.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiIcons.java
index 554f1e0f6dc2..c8e6b7bd725a 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiIcons.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiIcons.java
@@ -21,7 +21,9 @@ import java.util.List;
 /**
  * Single source of truth for emoji and symbolic icons used across the Camel 
TUI.
  * <p/>
- * Tab/menu icons use plain 2-column-wide emoji without VS16 variation 
selectors (see CAMEL-23818). Doctor and legacy
+ * Tab/menu icons use plain 2-column-wide emoji without VS16 variation 
selectors (CAMEL-23818). TamboUI 0.5.0 measures a
+ * base glyph + VS16 sequence as 2 columns (tamboui/tamboui#388), but the 
xterm.js Unicode 11 width tables that the
+ * {@code --web} frontend uses treat VS16 as zero-width, so such a sequence 
still misaligns there. Doctor and legacy
  * status glyphs may still use mixed-width symbols until migrated.
  */
 final class TuiIcons {
@@ -60,8 +62,8 @@ final class TuiIcons {
     // memo (📝) reads as "edit"; the letters glyph reads as changing the name
     static final String RENAME = "🔤";
     static final String DUPLICATE = "📑";
-    // NOTE: the wastebasket emoji (🗑) is width-ambiguous and TamboUI does not 
align it correctly yet, so use the
-    // cross-mark instead until that is fixed upstream.
+    // the wastebasket (🗑) is a text-default glyph: TamboUI counts it as 2 
columns while terminals draw it in 1 unless
+    // it carries VS16, which the --web frontend cannot measure (see the class 
javadoc), so use the cross-mark instead
     static final String DELETE = "❌";
 
     // ---- Actions menu ----
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/FlowHelperTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/FlowHelperTest.java
new file mode 100644
index 000000000000..d7daaa72fafd
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/FlowHelperTest.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.jbang.core.commands.tui;
+
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class FlowHelperTest {
+
+    @Test
+    void compactSizeFitsTheFourColumnAxisLabel() {
+        Map<Long, String> expected = Map.of(
+                0L, "0",
+                512L, "512",
+                999L, "999",
+                1024L, "1.0K",
+                1536L, "1.5K",
+                12288L, "12K",
+                1023488L, "1.0M", // 999.5 KB rounds up into the megabyte 
range rather than to "1000K"
+                1048576L, "1.0M",
+                1258291L, "1.2M",
+                15728640L, "15M");
+
+        expected.forEach((size, label) -> {
+            assertThat(FlowHelper.compactSize(size)).as("size %d", 
size).isEqualTo(label);
+            assertThat(label.length()).isLessThanOrEqualTo(4);
+        });
+    }
+
+    @Test
+    void throughputAxisLabelConvertsScaledRatesBackToMessagesPerSecond() {
+        // the chart data stays scaled by THROUGHPUT_SCALE, so the axis label 
has to undo the scaling
+        assertThat(MetricsCollector.formatThroughput(0)).isEqualTo("0");
+        assertThat(MetricsCollector.formatThroughput(20)).isEqualTo("0.20");
+        assertThat(MetricsCollector.formatThroughput(150)).isEqualTo("1.5");
+        assertThat(MetricsCollector.formatThroughput(700)).isEqualTo("7.0");
+        assertThat(MetricsCollector.formatThroughput(1500)).isEqualTo("15");
+        
assertThat(MetricsCollector.formatThroughput(1234500)).isEqualTo("12K");
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiBackendHelperRecordingTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiBackendHelperRecordingTest.java
index fa2e495ae974..c95f2a274b38 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiBackendHelperRecordingTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiBackendHelperRecordingTest.java
@@ -25,6 +25,7 @@ import dev.tamboui.internal.record.AnsiTerminalCapture;
 import dev.tamboui.layout.Position;
 import dev.tamboui.layout.Size;
 import dev.tamboui.terminal.Backend;
+import dev.tamboui.tui.TuiRunner;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
@@ -35,12 +36,12 @@ import static org.assertj.core.api.Assertions.assertThat;
 /**
  * Verifies that {@code camel tui --record} actually engages TamboUI's 
recording backend.
  * <p>
- * TamboUI only wraps a backend for recording inside {@code 
BackendFactory.create()}, and {@code TuiRunner} calls that
- * factory <em>only</em> when no explicit backend is configured. Since 
camel-tui must supply an explicit
- * {@link dev.tamboui.backend.jline3.JLineBackend} (auto-discovery would 
otherwise pick the Aesh backend that is on the
- * classpath for {@code --web}), the recording wrapper is never applied unless 
camel-tui applies it itself. When that
- * wrapping is missing, {@code --record} replays no tape and writes no {@code 
.cast} file, yet still exits cleanly, so
- * only a test like this one catches the regression.
+ * camel-tui must supply an explicit {@link 
dev.tamboui.backend.jline3.JLineBackend} (auto-discovery would otherwise
+ * pick the Aesh backend that is on the classpath for {@code --web}). Before 
TamboUI 0.5.0 only backends created by
+ * TamboUI's own factory were wrapped for recording, so camel-tui wrapped its 
explicit backend itself; since 0.5.0
+ * {@code TuiRunner.create} does that for explicit backends too. When the 
wrapping goes missing, {@code --record}
+ * replays no tape and writes no {@code .cast} file, yet still exits cleanly, 
so only a test like this one catches the
+ * regression.
  */
 // RecordingConfig.load() also reads fps and duration, so every key camel-tui 
sets has to be managed here: a value
 // leaking out of this class would silently reconfigure recording in an 
unrelated test. junit-pioneer clears each key
@@ -65,12 +66,12 @@ class TuiBackendHelperRecordingTest {
     }
 
     @Test
-    void withoutRecordOptionTheBackendIsHandedToTuiRunnerUntouched() {
+    void withoutRecordOptionTheBackendIsHandedToTuiRunnerUntouched() throws 
Exception {
         Backend original = new NoopBackend();
 
-        Backend result = TuiBackendHelper.applyRecording(original);
-
-        assertThat(result).isSameAs(original);
+        try (TuiRunner runner = TuiBackendHelper.createTuiRunner(original)) {
+            assertThat(runner.backend()).isSameAs(original);
+        }
     }
 
     @Test
@@ -82,13 +83,14 @@ class TuiBackendHelperRecordingTest {
         System.setProperty("tamboui.record.width", "120");
         System.setProperty("tamboui.record.height", "30");
 
-        Backend result = TuiBackendHelper.applyRecording(new NoopBackend());
-
-        // A recording backend reports the configured cast dimensions rather 
than the real terminal
-        // size; asserting on those proves the configuration was applied, not 
merely that some
-        // wrapper was returned.
-        assertThat(result).isNotInstanceOf(NoopBackend.class);
-        assertThat(result.size()).isEqualTo(new Size(120, 30));
+        try (TuiRunner runner = TuiBackendHelper.createTuiRunner(new 
NoopBackend())) {
+            // A recording backend reports the configured cast dimensions 
rather than the real terminal
+            // size; asserting on those proves the configuration was applied, 
not merely that some
+            // wrapper was returned.
+            Backend result = runner.backend();
+            assertThat(result).isNotInstanceOf(NoopBackend.class);
+            assertThat(result.size()).isEqualTo(new Size(120, 30));
+        }
     }
 
     /**
@@ -161,6 +163,11 @@ class TuiBackendHelperRecordingTest {
             return -2;
         }
 
+        @Override
+        public void writeRaw(byte[] data) throws IOException {
+            // TuiRunner.create enables bracketed paste through this method
+        }
+
         @Override
         public void close() throws IOException {
         }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiIconsTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiIconsTest.java
index 6ed452a76918..0fa31d88d4bd 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiIconsTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiIconsTest.java
@@ -24,9 +24,10 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
- * Validates {@link TuiIcons} primary tab emoji width (CAMEL-23818: avoid VS16 
mismeasurement in TamboUI) and the
- * mnemonic and runtime/platform icon helpers. More-submenu icons and labels 
are validated in {@link TabRegistryTest}
- * where the {@link TabRegistry.MoreTab} records that own them are constructed.
+ * Validates {@link TuiIcons} primary tab emoji width (CAMEL-23818: no VS16 
sequences, which the {@code --web} xterm.js
+ * width tables measure differently from TamboUI) and the mnemonic and 
runtime/platform icon helpers. More-submenu icons
+ * and labels are validated in {@link TabRegistryTest} where the {@link 
TabRegistry.MoreTab} records that own them are
+ * constructed.
  */
 class TuiIconsTest {
 

Reply via email to