Skip to content

Accessibility audit of Lite and the Viewer: name 1,400+ column filter, sidebar and tab buttons for UI Automation, and pin it - #4696

Merged
erikdarlingdata merged 5 commits into
devfrom
fix/a11y-audit-lite-viewer
Sep 29, 2026
Merged

erikdarlingdata merged 5 commits into
devfrom
fix/a11y-audit-lite-viewer

Conversation

@erikdarlingdata

@erikdarlingdata erikdarlingdata commented Sep 29, 2026 •

Copy link
Copy Markdown
Owner

Refs #4684 (the UI Automation pass that started this). No issue to close.

Why

A UI Automation (UIA) pass found #4684 (selecting the "+" plan tab adds two tabs). Every tab, button, menu item and toggle in Lite and the Darling Viewer is checked on three questions: can UIA and the keyboard reach it, does it have a name, and does UIA Invoke do what a click does. This is an audit from source (a Python scanner over every .xaml plus the code-behind that builds controls or wires mouse handlers; scratch only, not committed). There is no live walk yet.

Almost all of it was one defect. Every DataGrid column header carries a filter button whose only content is the Segoe MDL2 funnel glyph (U+E71C, a private-use character), so UIA gave roughly 1,400 buttons a name nobody can hear.

What changes

Names only (AutomationProperties.Name in XAML, AutomationProperties.SetName where the control is built in code). Nothing changes on screen and no theme key changes. Twin files got the same fix.

  • 1,411 column-filter buttons now read "Filter " (for example "Filter Run Time"). Lite: ServerTab, FinOpsTab, AlertsHistoryTab, JobHistoryTab, WaitDrillDownWindow. Viewer: ViewerServerTab, FinOpsTab, AlertsHistoryTab, JobHistoryTab, WaitDrillDownWindow. The name is a local value, so it survives the runtime swap to ColumnFilterButtonActiveStyle.
  • 52 more XAML names (44 in Lite and Viewer, 8 in the shared PerformanceMonitor.Ui): the sidebar buttons whose content is a glyph-plus-label StackPanel (Open Plan Viewer, Import Data, Import Settings, View Log, Open Log Folder, Settings, About, and the Viewer's Add Server, Add Multiple, Manage Servers, Manage Tags); "Toggle sidebar" on the << button; the Plan Viewer tab and its close button; Expander headers and CheckBoxes whose content is an element (AutomationProperties.Name="{Binding DisplayName}"); "Enabled" on the check boxes in the mute-rule and notification-route grids; the Time Range slicer toggle; and in PerformanceMonitor.Ui, Zoom In, Zoom Out, Close Properties (Blocking chain and Deadlock graph) and the calendar's Previous month / Next month arrows.
  • Import Data is bound to its own label ({Binding Text, ElementName=ImportDataButtonText}) because that label changes to "Importing..." while it runs.
  • 12 code-built controls (SetName): each server tab and its close button (Lite and Viewer MainWindow.xaml.cs), each plan sub-tab and its close button (ServerTab.Plans.cs, ViewerServerTab.Plans.cs; the Plan viewer: selecting the "+" tab through UI Automation adds two empty sub-tabs #4684 ToString shape), the wait drill-down and shared DataGrid filter buttons, and the theme colour picker's "..." button, named for its slot ("Pick a color for ") so the rows don't all read the same.
  • Pin, one source test per app: Lite.Tests/XamlAccessibleNameTests.cs and Darling.Tests/ViewerXamlAccessibleNameTests.cs, both over one shared scan (Darling.Tests/XamlAccessibleNames.cs, compiled into Lite.Tests by a link line like CSharpSourceWalker). Every Button, ToggleButton, MenuItem and TabItem outside a ControlTemplate needs AutomationProperties.Name / LabeledBy, or a Content / Header that is text (a binding or resource counts). An element tree or a string with no letter or digit does not count, which is exactly the Plan viewer: selecting the "+" tab through UI Automation adds two empty sub-tabs #4684 shape. The allowlist compares exactly in both directions and names the issue per entry.

Audit table (scanner, before then after this PR)

Interactive elements checked: Lite 1,278 (1,235 XAML elements, 24 code-built controls, 8 code-wired mouse handlers, 7 ContextMenu hosts, 4 code-built ContextMenus); Viewer 1,319 (1,274 / 21 / 12 / 8 / 4); shared Ui 76 (41 / 21 / 9 / 1 / 4). Scanned kinds: Button, RepeatButton, ToggleButton, CheckBox, RadioButton, MenuItem, TabItem, Hyperlink, ComboBox, Expander, plus anything with a MouseLeftButton/Mouse/PreviewMouse/DoubleClick handler.

Defect Lite Viewer Ui Disposition
1. No name (empty, glyph-only, element tree) 753 to 16 742 to 19 17 to 8 Fixed here. What is left: Lite 16 = 15 ControlTemplate parts in the three themes (not instances) + 1 scanner false positive (a MenuItem whose Header is a string built with new string(' ', n)); Viewer 19 = 18 ControlTemplate parts (theme files and two local ComboBox templates) + the same false positive; Ui 8 = all plan viewer (#4692)
1b. ComboBox with no name 46 51 1 Filed #4695 (needs a human check of each label)
2. Mouse-only (non-control with a mouse handler) 17 XAML + 8 code 16 + 12 9 + 9 Filed: #4693 (grid double-click), #4694 (cards, badges, links, swatches), #4692 (plan viewer)
2b. Interactive control with Focusable/IsTabStop false 6 9 0 Not a defect: all are ComboBox / DatePicker chrome parts inside ControlTemplates (the theme files and two local ComboBox templates)
3. Invoke mismatch (Button whose action is on a Mouse*/PreviewMouse* handler with no Click/Command) 0 0 0 Rule checked by the scanner; none found
3b. Button that works as a toggle 2 2 0 Filed in #4694 (sidebar toggle, slicer toggle): needs ToggleButton, a design change
4. MenuItem with no Header 0 0 0 None found
4b. ContextMenu on an element that cannot take focus 2 2 0 Filed in #4694 (MainWindow.xaml Borders)

After this PR the pin finds exactly 5 unnamed governed elements in each app, all in PerformanceMonitor.Ui/PlanViewerControl.xaml (lines 21, 23, 246, 334, 373: zoom in, zoom out and three close X buttons), owned by #4692.

Left alone on purpose (hands off, reported not fixed): StandalonePlanViewerController.cs (line 91 "+" tab header, 224 subCloseBtn, 231 subTab StackPanel header), PlanViewerControl.Rendering.cs (110 node click on MouseLeftButtonUp, 489-503 AttachOriginNavigation warning link on MouseLeftButtonDown), PlanViewerControl.Properties.cs:1072, PlanViewerControl.xaml (21, 23, 51, 246, 289, 322, 334, 340, 373), and the Viewer's Themes/*.xaml (PR #4685). No theme file was edited in either app.

Issues filed (no in-progress label)

Test plan

  • dotnet build of Lite.Tests and Darling.Tests: 0 Warning(s), 0 Error(s), before and after merging origin/dev.
  • New pins green: Lite.Tests.XamlAccessibleNameTests (1/1) and Darling.Tests.ViewerXamlAccessibleNameTests (1/1).
  • RED proved in both apps: removing AutomationProperties.Name="Toggle sidebar" from MainWindow.xaml failed each pin with NO NAME SOURCE (1 found, 0 allowed): ...MainWindow.xaml|Button|ToggleSidebar_Click; restored, both green again.
  • Darling.Tests classes starting with Viewer (after merging origin/dev): Total 2017, Failed 0, Skipped 127 (live PostgreSQL classes; no rig here).
  • Full Lite.Tests suite (after merging origin/dev): Total 5556, Failed 0, Skipped 0, Errors 0, 181 s.
  • Live UIA walk of Lite and the Viewer: confirm the names read as intended, and that Invoke on the tab close and sidebar buttons does what a click does.

Notes

  • Diff noise: the 1,411 filter-button edits are one-line attribute appends (1,411 lines changed, none added or removed) across ten XAML files; a scripted edit, checked by re-running the scanner (no ColumnFilterButtonStyle button is left unnamed).
  • Lite.Tests.csproj gains one link line for the shared scan (same idiom as CSharpSourceWalker).
  • Name wording ("Filter ", "Toggle sidebar", "Close ", "Close tab") follows the existing tooltips; the app has no localization, so names are plain English literals like every other UI string.

CHANGELOG

SECTION: Fixed
ENTRY:

erikdarlingdata and others added 5 commits September 28, 2026 20:10
…mation reads "Filter <column>"

Every DataGrid column header carries a ColumnFilterButtonStyle button whose only content is the
Segoe MDL2 funnel glyph (U+E71C, a private-use character), so UIA gave it a name no screen reader
can speak. Each button now carries AutomationProperties.Name="Filter <the header caption beside it>".
Applied alike to the Lite and Viewer twin files; the theme files are untouched.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NKkGR7ZBZavtGbVYcK58GC
…red icon buttons a UI Automation name in Lite and the Viewer

Content that is an element tree (a StackPanel holding a glyph and a label, a panel Header) leaves UIA with
the ToString() fallback, and glyph-only buttons (the "<<" sidebar toggle, +/- zoom, the close X, the
month arrows) were read out as the raw glyph. Each now carries AutomationProperties.Name, in XAML or
AutomationProperties.SetName where the control is built in code (server tabs, plan sub-tabs, their close
buttons, the wait drill-down and shared DataGrid filter buttons). Twin files got the same change.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NKkGR7ZBZavtGbVYcK58GC
…e and the Viewer has a UI Automation name source

One shared scan (Darling.Tests/XamlAccessibleNames.cs, linked into Lite.Tests like CSharpSourceWalker) and one
test per app. Content or Header that is an element tree, or a string with no letter or digit (a private-use
glyph, a plus sign, an X), does not count; AutomationProperties.Name / LabeledBy or real text does. The
allowlist is exact in both directions and names the issue for each exception: the five PlanViewerControl.xaml
glyph buttons (#4692, held by the plan-viewer lane). Proven RED by removing one name in each app.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NKkGR7ZBZavtGbVYcK58GC
Every row's picker was named "Pick a color", so a screen reader listed identical buttons. Each now reads "Pick a color for <slot label>".
@erikdarlingdata
erikdarlingdata marked this pull request as ready for review September 29, 2026 00:38
@erikdarlingdata
erikdarlingdata merged commit 9a0562d into dev Sep 29, 2026
18 of 20 checks passed
@erikdarlingdata
erikdarlingdata deleted the fix/a11y-audit-lite-viewer branch September 29, 2026 00:50
erikdarlingdata added a commit that referenced this pull request Sep 29, 2026
Reverts #4696 (9a0562d). This removes the UI Automation names #4696 added in Lite, the Darling Viewer and PerformanceMonitor.Ui, and the source-scan pins that required them (Lite.Tests.XamlAccessibleNameTests, Darling.Tests.ViewerXamlAccessibleNameTests and their shared XamlAccessibleNames scan). The tree matches dev as it was before #4696.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant