Skip to content

The desktop surface has no automation properties at all, so the rollup tiles fixed for the web have no screen-reader equivalent #3057

Closed as not planned

Description

@erikdarlingdata

This is parked, not queued. It needs a decision before it needs an implementation, and it is deliberately not workable until that decision exists.

#3045 tied every rollup figure on both web pages to the text that says what it counts, using aria-describedby. The desktop surface has the identical construct and no equivalent.

The fact that makes it concrete

AutomationProperties appears in zero files across all three desktop trees — measured on origin/dev:

tree files referencing AutomationProperties
Darling/PerformanceMonitor.Darling.Viewer 0
PerformanceMonitor.Ui 0
Lite 0

So this is not "seven tiles lack descriptions." The desktop surface has no automation properties at all, anywhere, in either SKU. That is a larger and more accurate statement, and it is the one that belongs in the record.

MainWindow.xaml's FleetRollupContainer (line ~778) is the same number-over-label construct the web fix was about — fourteen TextBlocks inside it, seven number/label pairs.

Why it is a decision rather than a task

Three things make it unlike #3045, and each cuts against just doing it:

  • UIA, not ARIA. The mechanism is AutomationProperties.Name / HelpText / LabeledBy, not aria-describedby. The web fix's reasoning does not port: the ARIA choice turned on .num being a plain div mapping to role=generic, a name-prohibited role. WPF has no analogous prohibition, so the right carrier has to be chosen fresh rather than inherited.
  • Seven hand-written tiles, not one closure. The web fix was cheap and safe precisely because both pages build every tile through one builder, which is what let a pin assert tiles == 1 && figures == 1 per page and catch a hand-rolled eighth. The XAML has seven independent tiles, so the equivalent fix is either seven hand edits with no guard against the eighth, or a restructuring into a templated item — and the second is a real design change to the fleet rollup.
  • Nothing establishes the intended baseline. With zero occurrences repo-wide there is no house convention to follow and no partial adoption to complete. Whether this product intends to support screen readers on the desktop at all is a product question, not an implementation detail, and answering it by adding properties to one container would answer it by accident.

What a decision looks like

Any of these closes it, and the first two are seconds of work:

  1. Won't fix — the desktop surface is not a screen-reader target. Legitimate, and worth recording so the question stops recurring.
  2. Park until someone asks — keep it open as a known gap with no owner.
  3. Restructure the fleet rollup into a templated item and wire UIA once, then guard it the way RollupTileDescriptionTests guards the web pages. This is the real work and it is not small.

Why this is an issue rather than a chip

It was chipped first and that was the wrong call. Three reasons it needed an artifact:

  • A chip is not durable. It lives until someone dismisses it and leaves nothing behind.
  • The scoping above IS the content. An issue whose scope is "this needs a decision before an implementation" is scoped, not unscoped — the parked bucket exists for exactly that.
  • A reviewer already claimed this gap does not exist. The automated review on Fixes #3045 #3054 asserted the desktop side has "no equivalent rollup-tile UI," which is flatly false, and it arrived as an issue comment rather than a review. Without something durable contradicting it, that comment is the record.

Provenance

Found by the #3045 lane while hoisting the shared helper, argued from chip to issue by fable-loop during #3054's review, and the zero-occurrence measurement re-run here across all three trees — it is broader than the Viewer alone, which is how it was first described.

Activity

  1. erikdarlingdata commented on Sep 6, 2026

    @erikdarlingdata
    OwnerAuthor

    Claude posting for Erik Darling

    Adding the site detail and answering the FinOpsTab question, since it changes the scope.

    FinOpsTab.xaml is clean, and ViewerServerTab.xaml is not

    Counting <TextBlock … FontSize="16" or larger — the "number" half of a number-over-label tile — across every XAML in the Viewer and PerformanceMonitor.Ui:

    file large-FontSize TextBlocks
    ViewerServerTab.xaml 12
    MainWindow.xaml 8
    FinOpsTab.xaml 0
    everything else 1 each

    So FinOpsTab does not have this shape — worth recording, because it was the file most likely to. The one-each files are almost certainly headers rather than tiles and should be confirmed as such rather than assumed, but ViewerServerTab.xaml at 12 is the larger site and needs a census before any fix is scoped. A fix that covers MainWindow and misses that file would leave the bigger half undone.

    I checked the strict Border > StackPanel > TextBlock(big) + TextBlock(small) pattern first and it returned zero for FinOpsTab and zero for MainWindow — which is wrong for MainWindow, whose tiles plainly are that shape. The regex was over-tight on whitespace. Recording that because a census done that way would report a clean sweep on a file that has five tiles.

    The confirmed shape in FleetRollupContainer

    Each of five tiles: Border (with a band-coloured BorderBrush) > StackPanel > TextBlock Text="{Binding TotalServers}" FontSize="22" FontWeight="Bold" over TextBlock Text="Servers" FontSize="10". Repeated for HealthyCount/Healthy, WarningCount/Warning, CriticalCount/Critical, OfflineCount/Offline.

    The mechanism question, stated as candidates rather than a choice

    UIA, not ARIA, and the web fix's reasoning does not transfer — it turned on .num being a plain div mapping to role=generic, a name-prohibited role. WPF has no analogous prohibition, so the carrier has to be chosen on its own merits:

    • AutomationProperties.HelpText on the number — closest analogue to aria-describedby
    • AutomationProperties.LabeledBy pointing at the label TextBlock
    • AutomationProperties.Name on the tile Border

    Which of these a screen reader actually announces for a non-focusable TextBlock inside a Border needs deciding, not assuming. That is the part no amount of source reading settles.

    Why the restructuring is the decision and not a detail

    The web tiles are built from one closure per page, which is why #3038 and PR #3054 were each a single edit no call site could opt out of — and why a pin could assert tiles == 1 && figures == 1 per page and catch a hand-rolled extra one. These are hand-written XAML, one Border per tile, so the same fix is N copies unless it goes into a Style/ControlTemplate or a small UserControl first. Doing that is most of the work, and without it there is no guard against the next hand-written tile.

    Prior art

    #105 ([A11Y] Accessibility compliance gaps) was closed as completed on 2026-02-22. Worth reading before deciding won't-fix here — whatever was concluded then is the closest thing to a stated position on whether this surface is a screen-reader target, and AutomationProperties being at zero across all three trees today is evidence about what that closure actually delivered.

    Unchanged

    No screen reader has been run on any of this, on either surface. PR #3054's "Not verified" section says so for the web pages and it applies here with more force, since the whole question is what UIA announces.

  2. erikdarlingdata commented on Sep 6, 2026

    @erikdarlingdata
    OwnerAuthor

    Claude posting for Erik Darling

    No PR. The gating decision this issue asks for already exists, in the negative — so the census and both design decisions are done and recorded here, and nothing was shipped.

    #105 is the decision, and it declined this

    Its body carries a Screen Reader Support section led by "Add AutomationProperties.Name to all buttons, icons, and status indicators". It was closed COMPLETED on 2026-02-22 with:

    Closing — most items aren't relevant for this audience. Keyboard shortcuts (Ctrl+R refresh, Ctrl+PgUp/PgDn tab cycling) are worth doing and will be picked up in a future polish pass.

    Keyboard shortcuts were carved out as worth doing. Screen readers were not. So AutomationProperties sitting at zero is not an oversight — it is what that closure delivered, deliberately. A repo-wide search for accessibility / a11y / AutomationProperties / screen-reader returns #105, this issue, and the two web ones; nothing has revisited it.

    Shipping desktop UIA now would reverse a stated owner decision on a product question, by agent — which is the "answering it by accident" this issue was filed to avoid.

    Counter-pressure, on the record: the web surface was wired in September 2026 for the same audience without revisiting #105. That weakens "not a target" as a blanket position but does not overturn it. The cost asymmetry is why the two were treated differently — one closure per web page versus 25 hand-written sites here.

    Census: 25 real pairs in 3 files, across BOTH SKUs, zero wired

    127 XAML files parsed with System.Xml.Linq, floored so a sweep reading nothing fails loudly.

    file pairs ordering
    Darling/PerformanceMonitor.Darling.Viewer/ViewerServerTab.xaml L2016–2052 10 label-first
    Lite/Controls/ServerTab.xaml L1476–1512 10 label-first
    Darling/PerformanceMonitor.Darling.Viewer/MainWindow.xaml L823–859 5 value-first

    Four corrections to what this issue and I previously said:

    1. MainWindow has 5 pairs, not seven. The earlier figure counted header and fleet-totals TextBlocks that are not tiles.
    2. ViewerServerTab's 10 pairs are INVERTED — label above value ("Physical Memory" FontSize=11 then PhysicalMemoryText FontSize=18). A value-first matcher returns zero tiles for that file. That is the same class of miss as the whitespace-tight regex recorded above, in the ordering dimension rather than the whitespace one. My "12" was a large-TextBlock count, not a pair count: 10 are strip values and 2 are the "No Plan Loaded" empty state.
    3. Lite was never censused and holds 10 of the 25. The earlier table covered the Viewer and PerformanceMonitor.Ui only, so a Viewer-only fix leaves an entire second SKU undone.
    4. FinOpsTab is clean in both SKUs, confirmed at FontSize floors 16 and 12. The one-each files are confirmed headers — literal static headings or single x:Named window titles — rather than assumed. Sensitivity check: dropping the floor 16→12 takes large TextBlocks 107→555 but pairs only 31→36, all chrome.

    Correction to this issue's own framing

    "The desktop surface has no automation properties at all" is wrong. GetHelpTextCore() falls back to ToolTip, and MainWindow's TotalDeadlocks figure already has one — so it already exposes UIA HelpText with zero AutomationProperties in the file. The surface sets none explicitly. The real gap is exactly #3031's: two sibling text elements, correct reading order, no programmatic association.

    Decision 1 — AutomationProperties.HelpText on the figure. The other two are eliminated by source, not preference.

    FrameworkElementAutomationPeer.GetNameCore() is one chain: AutomationProperties.Name → LabeledBy → ((FrameworkElement)Owner).GetPlainText(). TextBlockAutomationPeer does not override it, and TextBlock.GetPlainText() returns its own text. So every figure already has a UIA Name equal to its number, for free — and both Name and LabeledBy sit upstream of it.

    • LabeledBy on the figure is actively destructive. It pre-empts GetPlainText(), so the number announces as "Servers" and "12" is gone — worse than the status quo. Pointing it at the Border instead is a silent no-op, since GetLabeledByCore() is element.GetAutomationPeer() and Border has none.
    • Name on the figure evicts the same way, and only works as a composed bound string that duplicates the figure.
    • Name on the tile Border is inert. UIElement.OnCreateAutomationPeer() returns null and neither Border nor Decorator overrides it, so no UIA element exists to carry it.
    • HelpText is the only additive carrier — a separate property, so Name stays "12" and the description arrives alongside it.

    Same carrier as the web fix, independently derived and for a different reason. Web: role=generic is name-prohibited. WPF: no prohibition, but Name is already occupied by the figure and everything else is upstream of it.

    Decision 2 — ItemsControl + DataTemplate, or a UserControl. Never a ControlTemplate.

    TextBlockAutomationPeer.IsControlElementCore() returns a hard false when TemplatedParent is a templated control and not a ContentPresenter. So a ControlTemplate drops the figures out of the UIA control view entirely — the restructuring this issue calls "most of the work" would, in that form, delete the very figures it was meant to make announceable. DataTemplate (ContentPresenter parent) and UserControl (null parent) are safe. A setters-only Style is safe but cannot carry per-tile labels.

    Proposed split, if #105 is reversed: (1) MainWindow's 5 tiles → ItemsControl + DataTemplate with HelpText wired once, plus the guard — the FleetRollup VM already computes the counts; (2) ViewerServerTab's 10, which need a new item VM since those are code-behind-assigned; (3) Lite's identical 10, plus parity between the SKUs. Any guard must be derived from shipped XAML, floored, and ordering-blind — an ordering-blind pin is precisely what would have stopped the first census reading ViewerServerTab as clean.

    Not verified

    No screen reader was run. Same state PR #3054 recorded for the web pages, and it binds harder here because the whole question is what UIA announces. Nothing was built, run or rendered; no UIA tree was inspected; no BAML produced. Every announcement mechanic above is read from dotnet/wpf source, not observed. No code was written, so there is no mutation testing to report.

    The one question

    Does #105's "not relevant for this audience" still hold for the desktop SKUs now that the web is wired?

    • Yes → close this won't-fix citing [A11Y] Accessibility compliance gaps #105, with the census above as the durable record of what was declined and how large it is.
    • No → the three-PR split is scoped and both design decisions are made and sourced.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions