Skip to content

Commit b75d9c7

Browse files
committed
🤖 fix: Handle emojis with variation selectors in status_set
- Use Intl.Segmenter to count grapheme clusters instead of code points - Properly handles variation selectors (like ✏️), skin tones, and complex emojis - Remove text-sm class from error message in StatusSetToolCall - Add tests for emojis with variation selectors and skin tone modifiers
1 parent 1202238 commit b75d9c7

File tree

3 files changed

+42
-7
lines changed

3 files changed

+42
-7
lines changed

src/components/tools/StatusSetToolCall.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export const StatusSetToolCall: React.FC<StatusSetToolCallProps> = ({
3131
<Tooltip>status_set</Tooltip>
3232
</TooltipWrapper>
3333
<span className="text-muted-foreground italic">{args.message}</span>
34-
{errorMessage && <span className="text-error-foreground text-sm">({errorMessage})</span>}
34+
{errorMessage && <span className="text-error-foreground">({errorMessage})</span>}
3535
<StatusIndicator status={status}>{statusDisplay}</StatusIndicator>
3636
</ToolHeader>
3737
</ToolContainer>

src/services/tools/status_set.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,35 @@ describe("status_set tool validation", () => {
3131
}
3232
});
3333

34+
it("should accept emojis with variation selectors", async () => {
35+
const tool = createStatusSetTool(mockConfig);
36+
37+
// Emojis with variation selectors (U+FE0F)
38+
const emojis = ["✏️", "✅", "➡️", "☀️"];
39+
for (const emoji of emojis) {
40+
const result = (await tool.execute!({ emoji, message: "Test" }, mockToolCallOptions)) as {
41+
success: boolean;
42+
emoji: string;
43+
message: string;
44+
};
45+
expect(result).toEqual({ success: true, emoji, message: "Test" });
46+
}
47+
});
48+
49+
it("should accept emojis with skin tone modifiers", async () => {
50+
const tool = createStatusSetTool(mockConfig);
51+
52+
const emojis = ["👋🏻", "👋🏽", "👋🏿"];
53+
for (const emoji of emojis) {
54+
const result = (await tool.execute!({ emoji, message: "Test" }, mockToolCallOptions)) as {
55+
success: boolean;
56+
emoji: string;
57+
message: string;
58+
};
59+
expect(result).toEqual({ success: true, emoji, message: "Test" });
60+
}
61+
});
62+
3463
it("should reject multiple emojis", async () => {
3564
const tool = createStatusSetTool(mockConfig);
3665

src/services/tools/status_set.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,24 @@ export type StatusSetToolResult =
1818

1919
/**
2020
* Validates that a string is a single emoji character
21-
* Uses Unicode property escapes to match emoji characters
21+
* Uses Intl.Segmenter to count grapheme clusters (handles variation selectors, skin tones, etc.)
2222
*/
2323
function isValidEmoji(str: string): boolean {
24-
// Check if string contains exactly one character (handles multi-byte emojis)
25-
const chars = [...str];
26-
if (chars.length !== 1) {
24+
if (!str) return false;
25+
26+
// Use Intl.Segmenter to count grapheme clusters (what users perceive as single characters)
27+
// This properly handles emojis with variation selectors (like ✏️), skin tones, flags, etc.
28+
const segmenter = new Intl.Segmenter("en", { granularity: "grapheme" });
29+
const segments = [...segmenter.segment(str)];
30+
31+
// Must be exactly one grapheme cluster
32+
if (segments.length !== 1) {
2733
return false;
2834
}
2935

3036
// Check if it's an emoji using Unicode properties
31-
const emojiRegex = /^[\p{Emoji_Presentation}\p{Extended_Pictographic}]$/u;
32-
return emojiRegex.test(str);
37+
const emojiRegex = /^[\p{Emoji_Presentation}\p{Extended_Pictographic}]/u;
38+
return emojiRegex.test(segments[0].segment);
3339
}
3440

3541
/**

0 commit comments

Comments
 (0)