Current behavior
Pane output that contains flag emojis (e.g. π§π·, π¦π·) or other multi-codepoint grapheme clusters (e.g. ZWJ family sequences like π¨βπ©βπ§) renders as a blank space inside herdr panes, even when the host terminal renders them correctly outside herdr.
In src/pane/terminal.rs::ghostty_buffer_symbol_into, the symbol scratch is filled with the grapheme cluster, then validated against the cell's expected width:
// src/pane/terminal.rs:1035-1046
let expected_width = match wide {
crate::ghostty::CellWide::Wide => 2,
crate::ghostty::CellWide::Narrow | crate::ghostty::CellWide::SpacerHead => 1,
crate::ghostty::CellWide::SpacerTail => 0,
};
let actual_width = symbol_scratch.width();
if actual_width != expected_width
&& !(wide == crate::ghostty::CellWide::Narrow && actual_width == 2)
{
symbol_scratch.clear();
symbol_scratch.push_str(ghostty_blank_symbol_for_width(wide));
}
For a Regional Indicator pair like π§π· (U+1F1E7 U+1F1F7):
- Ghostty classifies the cell as
CellWide::Wide β expected_width = 2.
unicode_width::UnicodeWidthStr::width() sums per-codepoint widths β returns 4 (each RI codepoint is width 2 in isolation).
- The existing exception only covers
Narrow + actual_width == 2, so this case falls through and the cluster is wiped to " ".
Same path strips other multi-codepoint Wide clusters (ZWJ family/profession sequences, etc.).
Expected behavior
When Ghostty classifies the cell as Wide and the symbol is a multi-codepoint grapheme cluster whose summed codepoint widths exceed 2, herdr should trust the terminal's cell classification and emit the cluster as-is β symmetric with the existing Narrow && actual_width == 2 exception that already trusts the host for clusters like π³.
Concretely, an additional escape hatch in the same conditional:
if actual_width != expected_width
&& !(wide == crate::ghostty::CellWide::Narrow && actual_width == 2)
&& !(wide == crate::ghostty::CellWide::Wide
&& actual_width > 2
&& symbol_scratch.chars().count() > 1)
{
symbol_scratch.clear();
symbol_scratch.push_str(ghostty_blank_symbol_for_width(wide));
}
The chars().count() > 1 guard restricts the new exception to genuine multi-codepoint clusters and avoids accepting an accidentally-wide single char. No new dependency required.
Test additions in the existing ghostty_normalize_buffer_symbol_prefers_grapheme_width_when_metadata_disagrees test (src/pane/terminal.rs:1612):
const FLAG_GRAPHEME: &str = "π§π·";
const FAMILY_GRAPHEME: &str = "π¨βπ©βπ§";
assert_eq!(
ghostty_normalize_buffer_symbol(FLAG_GRAPHEME, crate::ghostty::CellWide::Wide),
FLAG_GRAPHEME
);
assert_eq!(
ghostty_normalize_buffer_symbol(FAMILY_GRAPHEME, crate::ghostty::CellWide::Wide),
FAMILY_GRAPHEME
);
Reproduction
- Open herdr.
- In any pane, run:
printf 'π§π· π¦π· π¨βπ©βπ§\n'
- Each cluster appears as a blank gap; the same command in the host terminal renders the flags/family glyphs correctly.
Impact
Any agent output, statusline, or shell prompt that uses flag or ZWJ emojis is silently stripped inside herdr panes. I hit this with a Claude Code statusline that uses country flags for a World Cup 2026 match section β the flags vanish when running under herdr.
Environment
- herdr 0.6.0
- macOS (darwin 25.4.0)
- Ghostty terminal
- zsh
Contribution intent
Current behavior
Pane output that contains flag emojis (e.g. π§π·, π¦π·) or other multi-codepoint grapheme clusters (e.g. ZWJ family sequences like π¨βπ©βπ§) renders as a blank space inside herdr panes, even when the host terminal renders them correctly outside herdr.
In
src/pane/terminal.rs::ghostty_buffer_symbol_into, the symbol scratch is filled with the grapheme cluster, then validated against the cell's expected width:For a Regional Indicator pair like π§π· (
U+1F1E7 U+1F1F7):CellWide::Wideβexpected_width = 2.unicode_width::UnicodeWidthStr::width()sums per-codepoint widths β returns4(each RI codepoint is width 2 in isolation).Narrow + actual_width == 2, so this case falls through and the cluster is wiped to" ".Same path strips other multi-codepoint Wide clusters (ZWJ family/profession sequences, etc.).
Expected behavior
When Ghostty classifies the cell as Wide and the symbol is a multi-codepoint grapheme cluster whose summed codepoint widths exceed 2, herdr should trust the terminal's cell classification and emit the cluster as-is β symmetric with the existing
Narrow && actual_width == 2exception that already trusts the host for clusters like π³.Concretely, an additional escape hatch in the same conditional:
The
chars().count() > 1guard restricts the new exception to genuine multi-codepoint clusters and avoids accepting an accidentally-wide single char. No new dependency required.Test additions in the existing
ghostty_normalize_buffer_symbol_prefers_grapheme_width_when_metadata_disagreestest (src/pane/terminal.rs:1612):Reproduction
Impact
Any agent output, statusline, or shell prompt that uses flag or ZWJ emojis is silently stripped inside herdr panes. I hit this with a Claude Code statusline that uses country flags for a World Cup 2026 match section β the flags vanish when running under herdr.
Environment
Contribution intent