Skip to content

Commit 0d11a8a

Browse files
authored
Add OpenClaw red-bot brand mark (#942)
Add a shared red-bot BrandMark control and replace lobster/generic robot brand marks across Hub, onboarding, dialogs, and Agent Events surfaces while keeping native Windows accent colors unchanged. Validation: GitHub Build and Test, CodeQL gate, Socket checks all passed; current-head visual/UI proof included in PR.
1 parent cc2834c commit 0d11a8a

12 files changed

Lines changed: 180 additions & 26 deletions
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<UserControl
3+
x:Class="OpenClawTray.Controls.BrandMark"
4+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
5+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
6+
AutomationProperties.AccessibilityView="Raw"
7+
IsHitTestVisible="False"
8+
IsTabStop="False">
9+
10+
<Image x:Name="MarkImage"
11+
Stretch="Uniform"
12+
AutomationProperties.AccessibilityView="Raw"/>
13+
</UserControl>
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using Microsoft.UI.Xaml;
2+
using Microsoft.UI.Xaml.Controls;
3+
using OpenClawTray.Helpers;
4+
5+
namespace OpenClawTray.Controls;
6+
7+
public sealed partial class BrandMark : UserControl
8+
{
9+
private const double DefaultMarkSize = 20d;
10+
11+
public static readonly DependencyProperty MarkSizeProperty =
12+
DependencyProperty.Register(
13+
nameof(MarkSize),
14+
typeof(double),
15+
typeof(BrandMark),
16+
new PropertyMetadata(DefaultMarkSize, OnMarkSizeChanged));
17+
18+
public BrandMark()
19+
{
20+
InitializeComponent();
21+
MarkImage.Source = BrandAssets.CreateRedBotMarkSource();
22+
ApplySize(MarkSize);
23+
}
24+
25+
public double MarkSize
26+
{
27+
get => (double)GetValue(MarkSizeProperty);
28+
set => SetValue(MarkSizeProperty, value);
29+
}
30+
31+
private static void OnMarkSizeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
32+
{
33+
if (d is BrandMark mark && e.NewValue is double size)
34+
mark.ApplySize(size);
35+
}
36+
37+
private void ApplySize(double size)
38+
{
39+
if (!double.IsFinite(size) || size < 0)
40+
size = DefaultMarkSize;
41+
42+
Width = size;
43+
Height = size;
44+
MarkImage.Width = size;
45+
MarkImage.Height = size;
46+
}
47+
}

src/OpenClaw.Tray.WinUI/Dialogs/PairingApprovalDialog.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Microsoft.UI.Xaml.Media;
44
using OpenClaw.Connection;
55
using OpenClaw.Shared;
6+
using OpenClawTray.Controls;
67
using OpenClawTray.Helpers;
78
using OpenClawTray.Services;
89
using System;
@@ -65,7 +66,7 @@ public PairingApprovalDialog(PairingApprovalCoordinator coordinator)
6566
var titleBar = new Grid { Height = 48, Padding = new Thickness(16, 0, 140, 0) };
6667
titleBar.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
6768
titleBar.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
68-
var titleIcon = new TextBlock { Text = "🦞", FontSize = 16, VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 8, 0) };
69+
var titleIcon = new BrandMark { MarkSize = 16, VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 8, 0) };
6970
Grid.SetColumn(titleIcon, 0);
7071
titleBar.Children.Add(titleIcon);
7172
var titleText = new TextBlock

src/OpenClaw.Tray.WinUI/Dialogs/RecordingConsentDialog.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using Microsoft.UI.Xaml.Controls;
33
using Microsoft.UI.Xaml.Media;
44
using OpenClaw.Shared.Capabilities;
5+
using OpenClawTray.Controls;
56
using OpenClawTray.Helpers;
67
using OpenClawTray.Services;
78
using System;
@@ -55,10 +56,9 @@ public RecordingConsentDialog(RecordingType type)
5556
titleBar.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
5657
titleBar.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
5758

58-
var titleIcon = new TextBlock
59+
var titleIcon = new BrandMark
5960
{
60-
Text = "🦞",
61-
FontSize = 16,
61+
MarkSize = 16,
6262
VerticalAlignment = VerticalAlignment.Center,
6363
Margin = new Thickness(0, 0, 8, 0)
6464
};

src/OpenClaw.Tray.WinUI/Dialogs/WelcomeDialog.cs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using Microsoft.UI.Xaml;
22
using Microsoft.UI.Xaml.Controls;
33
using Microsoft.UI.Xaml.Media;
4+
using OpenClawTray.Controls;
45
using OpenClawTray.Helpers;
56
using OpenClawTray.Services;
67
using System;
@@ -45,10 +46,9 @@ public WelcomeDialog()
4546
Spacing = 12,
4647
HorizontalAlignment = HorizontalAlignment.Center
4748
};
48-
headerPanel.Children.Add(new TextBlock
49+
headerPanel.Children.Add(new BrandMark
4950
{
50-
Text = "🦞",
51-
FontSize = 48
51+
MarkSize = 48
5252
});
5353
headerPanel.Children.Add(new TextBlock
5454
{
@@ -130,10 +130,9 @@ public WelcomeDialog()
130130
outerGrid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
131131

132132
var titleBar = new Grid { Padding = new Thickness(16, 0, 140, 0) };
133-
var titleIcon = new TextBlock
133+
var titleIcon = new BrandMark
134134
{
135-
Text = "🦞",
136-
FontSize = 20,
135+
MarkSize = 20,
137136
VerticalAlignment = VerticalAlignment.Center,
138137
Margin = new Thickness(0, 0, 10, 0)
139138
};
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using Microsoft.UI.Xaml.Media.Imaging;
2+
3+
namespace OpenClawTray.Helpers;
4+
5+
public static class BrandAssets
6+
{
7+
public const string RedBotMarkUri = "ms-appx:///Assets/Square44x44Logo.targetsize-256_altform-unplated.png";
8+
9+
public static BitmapImage CreateRedBotMarkSource() => new(new Uri(RedBotMarkUri));
10+
}

src/OpenClaw.Tray.WinUI/Helpers/FluentIconCatalog.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,6 @@ public static class FluentIconCatalog
7373
public const string ChevronR = "\uE76C"; // ChevronRight
7474
public const string Check = "\uE73E"; // CheckMark
7575

76-
// ── Brand placeholder ──────────────────────────────────────────
77-
public const string Brand = "🦞";
78-
7976
// ── Diagnostics page glyphs ────────────────────────────────────
8077
// Added 2026-05 for the Diagnostics surface
8178
// (src/OpenClaw.Tray.WinUI/Pages/DebugPage.xaml). Keep these here

src/OpenClaw.Tray.WinUI/Pages/AgentEventsPage.xaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
x:Class="OpenClawTray.Pages.AgentEventsPage"
44
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
55
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
6+
xmlns:controls="using:OpenClawTray.Controls"
67
xmlns:helpers="using:OpenClawTray.Helpers">
78

89
<Grid Padding="24,16,24,16" MaxWidth="900" HorizontalAlignment="Stretch">
@@ -140,7 +141,7 @@
140141
<!-- Empty state -->
141142
<StackPanel x:Name="EmptyState" Grid.Row="3"
142143
VerticalAlignment="Center" HorizontalAlignment="Center" Spacing="8">
143-
<TextBlock Text="🤖" FontSize="48" HorizontalAlignment="Center"/>
144+
<controls:BrandMark MarkSize="48" HorizontalAlignment="Center"/>
144145
<TextBlock x:Uid="AgentEventsPage_TextBlock_96" Text="No events in this session"
145146
Style="{StaticResource SubtitleTextBlockStyle}"
146147
HorizontalAlignment="Center"/>

src/OpenClaw.Tray.WinUI/Windows/HubWindow.xaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
x:Class="OpenClawTray.Windows.HubWindow"
44
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
55
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
6+
xmlns:controls="using:OpenClawTray.Controls"
67
xmlns:winex="using:WinUIEx"
78
Title="OpenClaw"
89
MinWidth="1000" MinHeight="400">
@@ -60,7 +61,10 @@
6061
</StackPanel>
6162

6263
<!-- Icon + Title -->
63-
<TextBlock Grid.Column="1" Text="🦞" FontSize="18" VerticalAlignment="Center" Margin="8,0,8,0" Translation="0,-1,0"/>
64+
<controls:BrandMark Grid.Column="1"
65+
MarkSize="18"
66+
VerticalAlignment="Center"
67+
Margin="8,0,8,0"/>
6468
<TextBlock x:Uid="TitleText" Grid.Column="2" x:Name="TitleText" Text="OpenClaw Windows Companion"
6569
FontSize="13" VerticalAlignment="Center"
6670
Style="{StaticResource CaptionTextBlockStyle}"/>
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
namespace OpenClaw.Tray.Tests;
2+
3+
public sealed class BrandMarkContractTests
4+
{
5+
private const string LobsterEmoji = "\ud83e\udd9e";
6+
private const string RobotEmoji = "\ud83e\udd16";
7+
private const string RedBotAssetName = "Square44x44Logo.targetsize-256_altform-unplated.png";
8+
private static string RepoRoot => TestRepositoryPaths.GetRepositoryRoot();
9+
10+
private static string ReadTrayFile(params string[] pathParts)
11+
=> File.ReadAllText(Path.Combine(new[] { RepoRoot, "src", "OpenClaw.Tray.WinUI" }.Concat(pathParts).ToArray()));
12+
13+
[Fact]
14+
public void BrandMark_UsesSharedRedBotImageAsset()
15+
{
16+
var xaml = ReadTrayFile("Controls", "BrandMark.xaml");
17+
var code = ReadTrayFile("Controls", "BrandMark.xaml.cs");
18+
var assets = ReadTrayFile("Helpers", "BrandAssets.cs");
19+
var redBotAssetPath = Path.Combine(
20+
RepoRoot,
21+
"src",
22+
"OpenClaw.Tray.WinUI",
23+
"Assets",
24+
RedBotAssetName);
25+
26+
Assert.Contains("<Image", xaml);
27+
Assert.Contains("AutomationProperties.AccessibilityView=\"Raw\"", xaml);
28+
Assert.Contains("IsHitTestVisible=\"False\"", xaml);
29+
Assert.Contains("IsTabStop=\"False\"", xaml);
30+
Assert.Contains("BrandAssets.CreateRedBotMarkSource()", code);
31+
Assert.Contains(RedBotAssetName, assets);
32+
Assert.True(File.Exists(redBotAssetPath), $"BrandMark asset is missing: {redBotAssetPath}");
33+
Assert.DoesNotContain("FontIcon", xaml);
34+
Assert.DoesNotContain($"Text=\"{LobsterEmoji}\"", xaml);
35+
}
36+
37+
[Fact]
38+
public void KnownBrandSurfaces_UseBrandMark()
39+
{
40+
Assert.Contains("controls:BrandMark", ReadTrayFile("Windows", "HubWindow.xaml"));
41+
Assert.Contains("controls:BrandMark", ReadTrayFile("Pages", "AgentEventsPage.xaml"));
42+
43+
var expectedCodeFiles = new[]
44+
{
45+
Path.Combine("Dialogs", "WelcomeDialog.cs"),
46+
Path.Combine("Dialogs", "PairingApprovalDialog.cs"),
47+
Path.Combine("Dialogs", "RecordingConsentDialog.cs"),
48+
};
49+
50+
foreach (var file in expectedCodeFiles)
51+
{
52+
Assert.Contains("new BrandMark", ReadTrayFile(file.Split(Path.DirectorySeparatorChar)));
53+
}
54+
}
55+
56+
[Fact]
57+
public void TrayWinUiSources_DoNotRenderEmojiBrandMarks()
58+
{
59+
var sourceRoot = Path.Combine(RepoRoot, "src", "OpenClaw.Tray.WinUI");
60+
var offenders = Directory
61+
.EnumerateFiles(sourceRoot, "*", SearchOption.AllDirectories)
62+
.Where(path => path.EndsWith(".cs", StringComparison.OrdinalIgnoreCase)
63+
|| path.EndsWith(".xaml", StringComparison.OrdinalIgnoreCase))
64+
.Where(path => !IsBuildArtifact(sourceRoot, path))
65+
.SelectMany(path => File.ReadLines(path)
66+
.Select((line, index) => (path, line, lineNumber: index + 1)))
67+
.Where(item => item.line.Contains(LobsterEmoji, StringComparison.Ordinal)
68+
|| item.line.Contains(RobotEmoji, StringComparison.Ordinal))
69+
.Select(item => $"{Path.GetRelativePath(RepoRoot, item.path)}:{item.lineNumber}")
70+
.ToArray();
71+
72+
Assert.True(
73+
offenders.Length == 0,
74+
"Use the shared BrandMark control instead of emoji brand marks:"
75+
+ Environment.NewLine
76+
+ string.Join(Environment.NewLine, offenders));
77+
78+
static bool IsBuildArtifact(string sourceRoot, string path)
79+
{
80+
var relative = Path.GetRelativePath(sourceRoot, path);
81+
return relative.StartsWith($"bin{Path.DirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase)
82+
|| relative.StartsWith($"obj{Path.DirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase);
83+
}
84+
}
85+
}

0 commit comments

Comments
 (0)