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 d5fd95871656793ae0c53f9edd3fba19f3ea80be
Author: Claus Ibsen <[email protected]>
AuthorDate: Sat Sep 19 10:30:04 2026 +0200

    CAMEL-24759: camel-jbang - TUI: web terminal measures VS16 emoji like 
TamboUI, restoring the CAMEL-23818 icons
    
    The --web frontend used the xterm.js Unicode 11 addon, whose tables
    treat the VS16 variation selector as zero-width, so a text-default
    glyph such as the keyboard or wastebasket took one column in the
    browser while TamboUI 0.5.0 reserves two (tamboui/tamboui#388). Swap
    the addon for @xterm/addon-unicode-graphemes 0.4.0, which promotes
    base + VS16 to a 2-column cell; every icon in TuiIcons now measures
    the same in xterm.js and in TamboUI's CharWidth.
    
    That makes the CAMEL-23818 workaround reversible: the keystrokes,
    tape recording and delete actions get their keyboard, record, stop
    and wastebasket icons back (with VS16, since terminals draw the bare
    glyphs in one column), and the no-VS16 test rule is replaced by one
    that requires those icons to carry VS16 and measure two columns.
    
    Verified in Chrome: the Screen submenu keeps its alignment and moving
    the selection over the VS16 rows leaves no stray characters.
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../dsl/jbang/core/commands/tui/ActionsPopup.java    |  2 +-
 .../camel/dsl/jbang/core/commands/tui/TuiIcons.java  | 17 ++++++++---------
 .../src/main/resources/tui/web/index.html            | 11 +++++++----
 .../main/resources/tui/web/vendor/LICENSE-xterm.txt  |  8 +++++---
 .../tui/web/vendor/xterm-addon-unicode-graphemes.js  |  2 ++
 .../tui/web/vendor/xterm-addon-unicode11.js          |  2 --
 .../dsl/jbang/core/commands/tui/TabRegistryTest.java |  3 +--
 .../dsl/jbang/core/commands/tui/TuiIconsTest.java    | 20 ++++++++++++--------
 8 files changed, 36 insertions(+), 29 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
index ca2caa18951d..817decfdaad7 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
@@ -1062,7 +1062,7 @@ class ActionsPopup {
                 ? TuiIcons.menuItem(TuiIcons.KEYSTROKES, "Hide Keystrokes")
                 : TuiIcons.menuItem(TuiIcons.KEYSTROKES, "Show Keystrokes");
         String tapeLabel = tapeRecordingActive.get()
-                ? TuiIcons.menuItem(TuiIcons.STOP, "Stop Tape Recording")
+                ? TuiIcons.menuItem(TuiIcons.STOP_RECORD, "Stop Tape 
Recording")
                 : TuiIcons.menuItem(TuiIcons.RECORD, "Start Tape Recording");
         List<ListItem> items = new ArrayList<>();
         items.add(ListItem.from("  .."));
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 c8e6b7bd725a..f3ee3e6a738d 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,10 +21,10 @@ 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 (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.
+ * Tab/menu icons are 2-column emoji. A text-default glyph such as the 
keyboard or the wastebasket must carry the VS16
+ * variation selector (U+FE0F): TamboUI counts the bare glyph as 2 columns 
while terminals draw it in 1, and with VS16
+ * both TamboUI 0.5.0 (tamboui/tamboui#388) and the xterm.js grapheme width 
tables used by {@code --web} agree on 2.
+ * Doctor and legacy status glyphs may still use mixed-width symbols until 
migrated.
  */
 final class TuiIcons {
 
@@ -62,18 +62,17 @@ final class TuiIcons {
     // memo (πŸ“) reads as "edit"; the letters glyph reads as changing the name
     static final String RENAME = "πŸ”€";
     static final String DUPLICATE = "πŸ“‘";
-    // 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 = "❌";
+    static final String DELETE = "πŸ—‘οΈ";
 
     // ---- Actions menu ----
     static final String GO_TO = "πŸ”";
     static final String MESSAGE = "πŸ“©";
-    static final String KEYSTROKES = "πŸ”€";
+    static final String KEYSTROKES = "⌨️";
     static final String SLEEP = "πŸ’€";
     static final String STOP = "πŸ›‘";
     static final String QUIT = "πŸšͺ";
-    static final String RECORD = "πŸ”΄";
+    static final String RECORD = "⏺️";
+    static final String STOP_RECORD = "⏹️";
     static final String DOCTOR = "🩺";
     static final String RESET = "πŸ”„";
     static final String CLEAN = "🧹";
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/web/index.html 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/web/index.html
index 4362ef184eaf..c9b009654e7c 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/web/index.html
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/web/index.html
@@ -143,7 +143,7 @@
 
   <script src="/vendor/xterm.js"></script>
   <script src="/vendor/xterm-addon-fit.js"></script>
-  <script src="/vendor/xterm-addon-unicode11.js"></script>
+  <script src="/vendor/xterm-addon-unicode-graphemes.js"></script>
   <script>
     (function () {
       'use strict';
@@ -199,9 +199,12 @@
 
           fitAddon = new FitAddon.FitAddon();
           term.loadAddon(fitAddon);
-          var unicode11Addon = new Unicode11Addon.Unicode11Addon();
-          term.loadAddon(unicode11Addon);
-          term.unicode.activeVersion = '11';
+          // Unicode 15 width tables with grapheme clustering, so a 
text-default glyph followed by
+          // VS16 (e.g. the keyboard or record icons) takes the same 2 columns 
as in tamboui.
+          // The Unicode 11 addon treats VS16 as zero-width and misaligns 
everything after it.
+          var unicodeGraphemesAddon = new 
UnicodeGraphemesAddon.UnicodeGraphemesAddon();
+          term.loadAddon(unicodeGraphemesAddon);
+          term.unicode.activeVersion = '15-graphemes';
           term.open(document.getElementById('terminal-container'));
 
           function sendInit() {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/web/vendor/LICENSE-xterm.txt
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/web/vendor/LICENSE-xterm.txt
index 9cad528cd98c..b849cd9b852c 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/web/vendor/LICENSE-xterm.txt
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/web/vendor/LICENSE-xterm.txt
@@ -14,10 +14,12 @@ 
https://github.com/xtermjs/xterm.js/tree/master/addons/addon-fit
 
 Copyright (c) 2019, The xterm.js authors (https://github.com/xtermjs/xterm.js)
 
-xterm-addon-unicode11 0.9.0 (xterm-addon-unicode11.js)
-https://github.com/xtermjs/xterm.js/tree/master/addons/addon-unicode11
+xterm-addon-unicode-graphemes 0.4.0 (xterm-addon-unicode-graphemes.js)
+https://github.com/xtermjs/xterm.js/tree/master/addons/addon-unicode-graphemes
 
-Copyright (c) 2019, The xterm.js authors (https://github.com/xtermjs/xterm.js)
+Copyright (c) 2023, The xterm.js authors (https://github.com/xtermjs/xterm.js)
+The bundled Unicode property tables are generated from
+https://github.com/PerBothner/unicode-properties (MIT).
 
 All are released under the MIT License:
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/web/vendor/xterm-addon-unicode-graphemes.js
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/web/vendor/xterm-addon-unicode-graphemes.js
new file mode 100644
index 000000000000..b887d1b3f0d2
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/web/vendor/xterm-addon-unicode-graphemes.js
@@ -0,0 +1,2 @@
+!function(e,t){"object"==typeof exports&&"object"==typeof 
module?module.exports=t():"function"==typeof 
define&&define.amd?define([],t):"object"==typeof 
exports?exports.UnicodeGraphemesAddon=t():e.UnicodeGraphemesAddon=t()}(globalThis,(()=>(()=>{"use
 strict";var 
e={106:(e,t,s)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.UnicodeGraphemeProvider=void
 0;const r=s(765),n=s(200);class 
i{constructor(e=!0){this.ambiguousCharsAreWide=!1,this.version=e?"15-graphemes":"15",this.handleGraph
 [...]
+//# sourceMappingURL=addon-unicode-graphemes.js.map
\ No newline at end of file
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/web/vendor/xterm-addon-unicode11.js
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/web/vendor/xterm-addon-unicode11.js
deleted file mode 100644
index 941130afb3ad..000000000000
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/web/vendor/xterm-addon-unicode11.js
+++ /dev/null
@@ -1,2 +0,0 @@
-!function(e,t){"object"==typeof exports&&"object"==typeof 
module?module.exports=t():"function"==typeof 
define&&define.amd?define([],t):"object"==typeof 
exports?exports.Unicode11Addon=t():e.Unicode11Addon=t()}(globalThis,(()=>(()=>{"use
 strict";var 
e={384:(e,t,s)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.UnicodeV11=void
 0;const 
r=s(765),n=[[768,879],[1155,1161],[1425,1469],[1471,1471],[1473,1474],[1476,1477],[1479,1479],[1536,1541],[1552,1562],[1564,1564],[1611,1631],[1648,1648
 [...]
-//# sourceMappingURL=addon-unicode11.js.map
\ No newline at end of file
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TabRegistryTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TabRegistryTest.java
index 081443e3dcc5..83036314dc37 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TabRegistryTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TabRegistryTest.java
@@ -114,10 +114,9 @@ class TabRegistryTest {
     }
 
     @Test
-    void moreTabIconsAreTwoColumnsWideWithoutVariationSelector() {
+    void moreTabIconsAreTwoColumnsWide() {
         for (TabRegistry.MoreTab mt : registry.moreTabs()) {
             assertEquals(2, CharWidth.of(mt.icon()), "Icon should be 2 
terminal columns wide: " + mt.icon());
-            assertFalse(mt.icon().contains("\uFE0F"), "Icon should not contain 
VS16 variation selector: " + mt.icon());
         }
     }
 
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 0fa31d88d4bd..60613d46491a 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
@@ -16,18 +16,19 @@
  */
 package org.apache.camel.dsl.jbang.core.commands.tui;
 
+import java.util.List;
+
 import dev.tamboui.text.CharWidth;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
-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: 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.
+ * Validates {@link TuiIcons} emoji widths (CAMEL-23818: every icon has to 
take the 2 columns TamboUI reserves for it,
+ * which for text-default glyphs means carrying VS16) 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 {
 
@@ -60,9 +61,12 @@ class TuiIconsTest {
     }
 
     @Test
-    void primaryTabEmojisHaveNoVariationSelector() {
-        for (String icon : TuiIcons.PRIMARY_TAB_ICONS) {
-            assertFalse(icon.contains("\uFE0F"), "Icon should not contain VS16 
variation selector: " + icon);
+    void textDefaultMenuEmojisCarryVs16AndAreTwoColumnsWide() {
+        // bare U+2328, U+23F9, U+23FA and U+1F5D1 render in one column on 
terminals; the VS16 sequence is what
+        // makes the terminal, the --web xterm.js tables and TamboUI agree on 
two
+        for (String icon : List.of(TuiIcons.KEYSTROKES, TuiIcons.RECORD, 
TuiIcons.STOP_RECORD, TuiIcons.DELETE)) {
+            assertTrue(icon.endsWith("\uFE0F"), "Icon should end with the VS16 
variation selector: " + icon);
+            assertEquals(2, CharWidth.of(icon), "Icon should be 2 terminal 
columns wide: " + icon);
         }
     }
 

Reply via email to