From 19fcc29f0b03696afb92f0134aa74f0826d9b964 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 26 Oct 2026 21:24:37 +0000 Subject: [PATCH] Read a linear table one line per row, leaving empty cells out (2.24.0) A screen reader heard WHO as four lines per player, with a bare "Doing:" for every empty cell. Each row is now one line of labelled values, comma-separated, and empty cells are left out with their labels. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01Wy1Vu4ZXX4NeZVnrnUiQbs --- CHANGELOG.md | 9 +++++++++ .../Layout/FieldsAndTreeTests.cs | 19 ++++++++++++++++++- MarkupString/Layout/Blocks/Table.cs | 19 +++++++++++++++++-- 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5617194..3ef12d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,15 @@ and `MarkupString.Pueblo`. The packages share one version and are released toget The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 2.24.0 — Unreleased + +### Changed + +- **A `Table` read linearly is one line per row**: its labelled values, comma-separated + (`Player Name: Dave, On For: 4s`), where it was one line per cell. A cell with nothing in it is left + out with its label, so a screen reader no longer reads a bare `Doing:`. A column with no heading reads + its value alone. A table too narrow for its columns still draws its rows as cards. + ## 2.23.0 — Unreleased ### Added diff --git a/MarkupString.Tests/Layout/FieldsAndTreeTests.cs b/MarkupString.Tests/Layout/FieldsAndTreeTests.cs index b8e8b85..2331885 100644 --- a/MarkupString.Tests/Layout/FieldsAndTreeTests.cs +++ b/MarkupString.Tests/Layout/FieldsAndTreeTests.cs @@ -101,7 +101,24 @@ public async Task Table_Linear_LabelsEachCellWithoutTheHeadingsPadding() [new TableColumn(P("Player")), new TableColumn(P("Idle ")) { Alignment = Alignment.Right }], [[new TextBlock(P("Mannaz")), new TextBlock(P("59s"))]]); - await Assert.That(Lines(table, 40, new LayoutContext { Linear = true })).IsEquivalentTo(new[] { "Player: Mannaz", "Idle: 59s" }); + await Assert.That(Lines(table, 40, new LayoutContext { Linear = true })).IsEquivalentTo(new[] { "Player: Mannaz, Idle: 59s" }); + } + + [Test] + public async Task Table_Linear_ReadsEachRowAsOneLine_LeavingEmptyCellsOut() + { + var table = new Table( + [new TableColumn(P("Player Name")), new TableColumn(P("On For")), new TableColumn(P("Doing")), new TableColumn(P(""))], + [ + [new TextBlock(P("Dave")), new TextBlock(P("4s")), new TextBlock(P("")), new TextBlock(P("[To do]"))], + [new TextBlock(P("Bob")), new TextBlock(P("1m")), new TextBlock(P("Exploring"))], + ]); + + await Assert.That(Lines(table, 60, new LayoutContext { Linear = true })).IsEquivalentTo(new[] + { + "Player Name: Dave, On For: 4s, [To do]", + "Player Name: Bob, On For: 1m, Doing: Exploring", + }); } private static Tree Channels(TreeGuide guide) => new( diff --git a/MarkupString/Layout/Blocks/Table.cs b/MarkupString/Layout/Blocks/Table.cs index ad009b8..616bc28 100644 --- a/MarkupString/Layout/Blocks/Table.cs +++ b/MarkupString/Layout/Blocks/Table.cs @@ -82,10 +82,25 @@ public override void Draw(LayoutContext context, int width, IList li } } - /// + /// + /// Each row as one line of labelled values, comma-separated, so a screen reader hears a row where the eye + /// sees one. A cell with nothing in it is left out with its label: a reader saying "Doing:" says nothing. + /// A column with no heading reads its value alone. + /// public override void DrawLinear(LayoutContext context, int width, IList lines) { - if (!Columns.IsDefaultOrEmpty) DrawCards(context, width, lines); + if (Columns.IsDefaultOrEmpty) return; + var values = context with { TextAlignment = Alignment.Left }; + foreach (var row in AllRows) + { + var parts = Enumerable.Range(0, Columns.Length) + .Select(c => (Label: Columns[c].Header.Trim(TrimType.TrimBoth), Value: MarkupText.Join(MarkupText.Plain(" "), values.Lines(Cell(row, c), int.MaxValue / 2).Select(line => line.Trim(TrimType.TrimBoth))))) + .Where(part => part.Value.ToPlainText().Trim().Length > 0) + .Select(part => part.Label.Length == 0 ? part.Value : MarkupText.Concat([part.Label, MarkupText.Plain(": "), part.Value])); + var line = MarkupText.Join(MarkupText.Plain(", "), parts); + if (line.Length == 0) continue; + foreach (var wrapped in values.Lines(new TextBlock(line), width)) lines.Add(wrapped); + } } /// Each row as a card of labelled values, the way a phone shows a table too wide for it.