diff --git a/src/OpenClaw.Tray.WinUI/Chat/OpenClawChatTimeline.cs b/src/OpenClaw.Tray.WinUI/Chat/OpenClawChatTimeline.cs index 44d40eb8d..4eea1e3d1 100644 --- a/src/OpenClaw.Tray.WinUI/Chat/OpenClawChatTimeline.cs +++ b/src/OpenClaw.Tray.WinUI/Chat/OpenClawChatTimeline.cs @@ -141,6 +141,9 @@ private static readonly System.Runtime.CompilerServices.ConditionalWeakTable s_monoFontByDispatcher = new(); + private const string ChatTextFontFamilySource = "Segoe UI Variable Text, Segoe UI"; + private static readonly System.Runtime.CompilerServices.ConditionalWeakTable< + Microsoft.UI.Dispatching.DispatcherQueue, FontFamily> s_chatTextFontByDispatcher = new(); private static FontFamily s_monoFontFamily { get @@ -158,6 +161,23 @@ private static FontFamily s_monoFontFamily return family; } } + private static FontFamily s_chatTextFontFamily + { + get + { + var dispatcher = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread(); + if (dispatcher is null) + { + return new FontFamily(ChatTextFontFamilySource); + } + if (!s_chatTextFontByDispatcher.TryGetValue(dispatcher, out var family)) + { + family = new FontFamily(ChatTextFontFamilySource); + s_chatTextFontByDispatcher.Add(dispatcher, family); + } + return family; + } + } // Per-DispatcherQueue selection-highlight brushes for the user // bubble. The bubble background is the user's chosen system accent @@ -1117,6 +1137,14 @@ Element RenderUserEntry(ChatTimelineItem entry, bool startsBurst, bool endsBurst t.FontSize = 14; t.Foreground = userBubbleFg; t.IsTextSelectionEnabled = true; + t.FontFamily = s_chatTextFontFamily; + t.TextTrimming = TextTrimming.None; + t.MaxLines = 0; + t.LineHeight = 0; + t.CharacterSpacing = 0; + t.Width = double.NaN; + t.MinWidth = 0; + t.MaxWidth = double.PositiveInfinity; // The default SelectionHighlightColor is the // system accent — which equals the user bubble's // background — so the highlight band is invisible diff --git a/tests/OpenClaw.Tray.Tests/ChatUserBubbleTextContractTests.cs b/tests/OpenClaw.Tray.Tests/ChatUserBubbleTextContractTests.cs new file mode 100644 index 000000000..e61f3b6b7 --- /dev/null +++ b/tests/OpenClaw.Tray.Tests/ChatUserBubbleTextContractTests.cs @@ -0,0 +1,26 @@ +using System.Text.RegularExpressions; + +namespace OpenClaw.Tray.Tests; + +public sealed class ChatUserBubbleTextContractTests +{ + [Fact] + public void UserPromptText_ResetsStaleTextBlockVisualStateBeforeApplyingInlines() + { + var timeline = Read("src", "OpenClaw.Tray.WinUI", "Chat", "OpenClawChatTimeline.cs"); + + Assert.Contains("private const string ChatTextFontFamilySource", timeline); + Assert.Contains("TextBlock(string.Empty)", timeline); + Assert.Contains("t.FontFamily = s_chatTextFontFamily;", timeline); + Assert.Contains("t.TextTrimming = TextTrimming.None;", timeline); + Assert.Contains("t.MaxLines = 0;", timeline); + Assert.Contains("t.Width = double.NaN;", timeline); + Assert.Contains("t.MaxWidth = double.PositiveInfinity;", timeline); + Assert.Matches( + new Regex(@"t\.TextTrimming\s*=\s*TextTrimming\.None;[\s\S]*ApplyPlainSelectableInlines\(t,\s*messageText\);"), + timeline); + } + + private static string Read(params string[] parts) + => File.ReadAllText(Path.Combine(new[] { TestRepositoryPaths.GetRepositoryRoot() }.Concat(parts).ToArray())); +}