Skip to content

Commit 8434453

Browse files
karkarlCopilot
andcommitted
Composer: neutral Stop fill + announce picker selection in a11y name
- Stop button now uses TextFillColorPrimaryBrush (theme-adaptive neutral) instead of the system accent; glyph uses SolidBackgroundFillColorBaseBrush so it stays legible against the inverted fill in light/dark. Keeps Send as the only accent control. Hover/pressed use TextFillColor secondary/tertiary. - PickerButton folds the current selection into its AutomationName as "field: value", restoring the selection announcement the legacy ComboBox provided via SelectionPattern. Fixes the SessionTitleBehaviorProof UI test (Category=Accessibility) that read the selected route from the session selector; updated it to read the redesigned menu-flyout Button. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 7e141ec commit 8434453

2 files changed

Lines changed: 33 additions & 22 deletions

File tree

src/OpenClaw.Tray.WinUI/Chat/OpenClawComposer.cs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1276,6 +1276,14 @@ Element PickerButton(string label, string automationName, double? maxLabelWidth,
12761276
t.Foreground = mutedBrush;
12771277
t.VerticalAlignment = VerticalAlignment.Center;
12781278
});
1279+
// Fold the current selection into the accessible name so assistive
1280+
// tech announces "<field>: <value>" (e.g. "Session: <title>"), the
1281+
// way the legacy ComboBox surfaced its selected item. The visible
1282+
// chevron button only shows the value, so without this the current
1283+
// selection would be silent to screen readers.
1284+
var accessibleName = string.IsNullOrWhiteSpace(label)
1285+
? automationName
1286+
: $"{automationName}: {label}";
12791287
return Button(HStack(4, labelBlock, chevron))
12801288
.Set(b =>
12811289
{
@@ -1293,7 +1301,7 @@ Element PickerButton(string label, string automationName, double? maxLabelWidth,
12931301
.Set("ButtonBorderBrushPointerOver", new SolidColorBrush(Colors.Transparent))
12941302
.Set("ButtonBorderBrushPressed", new SolidColorBrush(Colors.Transparent)))
12951303
.WithFlyout(menu)
1296-
.AutomationName(automationName)
1304+
.AutomationName(accessibleName)
12971305
.SetToolTip(automationName);
12981306
}
12991307

@@ -1458,8 +1466,9 @@ Element PickerButton(string label, string automationName, double? maxLabelWidth,
14581466

14591467
// Stop button — occupies the SAME action slot as Send (identical size)
14601468
// while the assistant is responding, so nothing in the toolbar shifts.
1461-
// Carries the system accent (Stop is the primary "act here" while a turn
1462-
// is active); red is reserved for genuine error states.
1469+
// Uses a neutral text-primary fill (theme-adaptive black/white); red is
1470+
// reserved for genuine error states. The glyph uses the base surface
1471+
// brush so it stays legible against the inverted fill in both themes.
14631472
Element stopBtn = Empty();
14641473
if (Props.TurnActive)
14651474
{
@@ -1471,19 +1480,19 @@ Element PickerButton(string label, string automationName, double? maxLabelWidth,
14711480
t.FontFamily = FluentIconCatalog.SymbolThemeFontFamily;
14721481
t.FontSize = composerIconSize;
14731482
})
1474-
.Foreground((Brush)Microsoft.UI.Xaml.Application.Current.Resources["TextOnAccentFillColorPrimaryBrush"]),
1483+
.Foreground((Brush)Microsoft.UI.Xaml.Application.Current.Resources["SolidBackgroundFillColorBaseBrush"]),
14751484
Props.OnStop
14761485
).Set(b =>
14771486
{
14781487
b.Padding = new Thickness(0);
14791488
b.MinWidth = 40; b.MinHeight = 32; b.Height = 32;
14801489
b.CornerRadius = controlCornerRadius;
1481-
b.Background = sendBrush;
1490+
b.Background = (Brush)Microsoft.UI.Xaml.Application.Current.Resources["TextFillColorPrimaryBrush"];
14821491
})
14831492
.Resources(r =>
14841493
{
1485-
r.Set("ButtonBackgroundPointerOver", Ref("AccentFillColorSecondaryBrush"));
1486-
r.Set("ButtonBackgroundPressed", Ref("AccentFillColorTertiaryBrush"));
1494+
r.Set("ButtonBackgroundPointerOver", Ref("TextFillColorSecondaryBrush"));
1495+
r.Set("ButtonBackgroundPressed", Ref("TextFillColorTertiaryBrush"));
14871496
r.Set("ButtonBorderBrush", new SolidColorBrush(Colors.Transparent));
14881497
r.Set("ButtonBorderBrushPointerOver", new SolidColorBrush(Colors.Transparent));
14891498
r.Set("ButtonBorderBrushPressed", new SolidColorBrush(Colors.Transparent));

tests/OpenClaw.Tray.UITests/SessionTitleBehaviorProofTests.cs

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,13 @@ private string WaitForSelectedSession(string expectedRouteTitle)
122122
var composerCondition = new PropertyCondition(
123123
AutomationElement.AutomationIdProperty,
124124
"ChatComposerInput");
125-
var sessionSelectorCondition = new AndCondition(
126-
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ComboBox),
127-
new PropertyCondition(AutomationElement.NameProperty, "Session"));
125+
// The redesigned session selector is a subtle menu-flyout Button (not a
126+
// ComboBox). Its accessible name folds in the current selection as
127+
// "Session: <route title>", replacing the legacy ComboBox's
128+
// SelectionPattern. Match on the route title so the exact field-label
129+
// prefix/separator format stays free to change.
130+
var buttonCondition = new PropertyCondition(
131+
AutomationElement.ControlTypeProperty, ControlType.Button);
128132

129133
string? selectedRouteTitle = null;
130134
WaitUntil(() =>
@@ -133,21 +137,19 @@ private string WaitForSelectedSession(string expectedRouteTitle)
133137
if (hub.FindFirst(TreeScope.Descendants, composerCondition) is null)
134138
return false;
135139

136-
var selector = hub.FindFirst(TreeScope.Descendants, sessionSelectorCondition);
137-
if (selector is null
138-
|| !selector.TryGetCurrentPattern(SelectionPattern.Pattern, out var pattern)
139-
|| pattern is not SelectionPattern selection)
140+
var buttons = hub.FindAll(TreeScope.Descendants, buttonCondition);
141+
for (var i = 0; i < buttons.Count; i++)
140142
{
141-
return false;
143+
var name = buttons[i].Current.Name;
144+
if (!string.IsNullOrEmpty(name)
145+
&& name.Contains(expectedRouteTitle, StringComparison.Ordinal))
146+
{
147+
selectedRouteTitle = name;
148+
return true;
149+
}
142150
}
143151

144-
selectedRouteTitle = selection.Current.GetSelection()
145-
.Select(item => item.Current.Name)
146-
.SingleOrDefault();
147-
return string.Equals(
148-
selectedRouteTitle,
149-
expectedRouteTitle,
150-
StringComparison.Ordinal);
152+
return false;
151153
}, $"chat Session selector to choose '{expectedRouteTitle}'");
152154

153155
return Assert.IsType<string>(selectedRouteTitle);

0 commit comments

Comments
 (0)