From 094a52ae81b74e8b62562f30bcde6b440ce925e9 Mon Sep 17 00:00:00 2001 From: Harry Cordewener Date: Sun, 16 Aug 2026 22:27:57 -0500 Subject: [PATCH 01/53] Say a graphic once, and draw a week only when there is one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An accessibility review of the site found the opposite of what such a review usually finds: nothing missing, everything said two or three times. The game page was the worst of it, and this is the first two of its three blocking findings. The connect screen was in the document three times — a 24-row excerpt, the whole thing again under "show all N rows", and its text a third time under "read as text". Three passes through box-drawing characters that carry no words, and the largest block on the page. It is now one frame holding every row, which scrolls; the crop is gone from the parser as well as from the frame, so there is no second copy to keep in step. The art is one image with a one-line alternative (role="img", which prunes the drawing the way aria-hidden would and, unlike aria-hidden, leaves the region focusable — it scrolls, and a keyboard has to be able to scroll it). The words are under "read as text", once. The alternative names the screen and points at the text; it does not describe the artwork. A paragraph about somebody else's ASCII would be our reading of their drawing presented as a fact about their game. The activity grid announced "not measured" 167 times on a game with one probe, before reaching the single number that existed. Two changes. Below seven measured days there is no grid at all — the panel says what we have and what has to arrive before there is a week to draw, which is kinder to a screen reader and stops a sparse page looking broken to everybody else. The summary sentence is suppressed with it: "busiest Monday, small hours" off one Monday morning is a claim about a shape one measurement cannot have. Where the grid is drawn it is hidden from assistive tech and its text alternative is a real table of seven rows — day, quietest, busiest, the hour of the peak, and a column each for the two kinds of hour that produce no number. The mouse tooltips are unchanged. Nothing about the three states of §5.4 is softened: an hour we could not count and an hour nobody measured have separate columns, and neither is ever a nought. The plain surface takes the same threshold and the same words, because it is the mirror people actually rely on. The capability tally was the section's heading text and, verbatim, the table's sr-only caption. It is now the caption, on screen, once. Co-Authored-By: Claude Opus 5 --- src/MUI.Web/Components/ActivityHeatmap.razor | 122 +++++++++++---- src/MUI.Web/Components/ActivitySummary.cs | 145 ++++++++++++++++-- src/MUI.Web/Components/Ansi.cs | 20 +-- src/MUI.Web/Components/AnsiQuote.razor | 63 ++++---- src/MUI.Web/Components/CapabilityMatrix.razor | 18 +-- src/MUI.Web/Components/Pages/Game.razor | 1 + src/MUI.Web/Components/PlainText.cs | 21 ++- src/MUI.Web/wwwroot/app.css | 51 ++++-- tests/MUI.Web.Tests/AnsiTests.cs | 21 +-- tests/MUI.Web.Tests/CapabilityMatrixTests.cs | 13 +- tests/MUI.Web.Tests/PlainParityTests.cs | 7 +- tests/MUI.Web.Tests/ThreeStatesTests.cs | 88 +++++++++-- 12 files changed, 423 insertions(+), 147 deletions(-) diff --git a/src/MUI.Web/Components/ActivityHeatmap.razor b/src/MUI.Web/Components/ActivityHeatmap.razor index 221791e..4f3a9da 100644 --- a/src/MUI.Web/Components/ActivityHeatmap.razor +++ b/src/MUI.Web/Components/ActivityHeatmap.razor @@ -1,26 +1,45 @@ @* The activity heatmap: day-of-week × hour, in three states. - A real with row and column headers, so a screen reader's table mode — which is arrow-key - navigation — works on it without a line of script. But the sentence comes first: it is the - accessible summary and the answer a reader actually wants, and the "read as text" disclosure - under the grid gives a line per day rather than 168 announced cells. + Three layers, in the order a listener meets them. **The sentence** is the answer — when is + anyone actually on — and most readers need nothing after it. **The table** under "read as text" + is seven rows, one per day, and is the drawing's text alternative: every fact the grid carries + is in it. **The drawing itself** is hidden from assistive tech, because it now says nothing the + first two do not, and announced cell by cell it is 168 utterances to deliver one shape. + + Below seven measured days there is no drawing at all. A grid with one probe in it announced + "not measured" 167 times before reaching the single number that existed, and looked like a fault + to everybody else. Flat fill for counted (including a measured zero), hatched outline for probed-but-uncountable, empty for not-reachable. That distinction is the whole of spec §5.4, it is carried by cell shape and not by colour, and it survives greyscale printing. *@ -

@ActivitySummary.Sentence(Cells)

-

times in UTC · @Window

- -@if (Cells.Count > 0) +@if (Cells.Count == 0) +{ +

@ActivitySummary.Sentence(Cells)

+} +else if (!Enough) +{ + @* + The panel replaces the summary rather than following it. On two measured hours the summary + reads "busiest Monday, small hours" — a claim about a week, drawn from a Monday morning, and + the one kind of sentence this site may not write. What there is, and what has to arrive + before there is a shape to describe. + *@ +
+

not enough measurements yet

+

@ActivitySummary.Sparse(Cells)

+
+} +else { -
+

@ActivitySummary.Sentence(Cells)

+

times in UTC · @Window

+ +
- @@ -43,11 +62,10 @@ @foreach (var hour in Hours) { var cell = Cell(day, hour); - @* The value is announced; the whole sentence is the mouse tooltip. The - headers already say which hour of which day this is. *@ - + @* The whole sentence is the mouse tooltip. The cell carries no announced + text: this grid is hidden from assistive tech and the seven rows under + it hold the same facts in seven utterances rather than 168. *@ + } } @@ -55,20 +73,54 @@
- Players on by day of the week and hour, in UTC. @ActivitySummary.Sentence(Cells) -
- @ActivitySummary.CellValue(cell) -
-