From 27a8cf3badca4d2ae942d37fc545d042b1140276 Mon Sep 17 00:00:00 2001 From: laurentiu021 Date: Fri, 4 Sep 2026 18:00:12 +0300 Subject: [PATCH] fix: stop report cells accepting typing that goes nowhere Ten text cells across App Updates and Windows Update entered edit mode on a double-click. Typing changed the in-memory row and nothing else, so the app appeared to accept an edit it discarded. One of them is App Updates' Id, which is what WingetService.UpgradeAsync builds "winget upgrade --id" from -- retyping it turns a row that would have upgraded into a row that reports an error. Per-column, NOT grid-level. #2105 read this as "20 views set IsReadOnly on the DataGrid, these forgot", which was backwards: every grid lacking it has a DataGridCheckBoxColumn for row selection, and DataGrid.IsReadOnly="True" renders those checkboxes untickable -- it would have disabled Upgrade selected. None of the 20 read-only grids has a checkbox column, which is why they can afford it. The issue's count was also inflated. Its pattern matched as well -- a property element, not a column, and there are 43 of them -- so 27 typeable cells across seven views was really 10 across two. Shortcut Cleaner, Uninstaller, Context Menu and Startup already carry the attribute per column. Environment Variables' Value column stays editable and is named as the exception with its reason: that tab is an editor, with Apply/Discard/Restore beside the grid and UpdateSourceTrigger=PropertyChanged carrying each keystroke to the view-model. Guarded by EveryReportTextColumn_IsReadOnly, floor 118 of 125 measured, with the property-element trap written into the remark so the next measurement avoids it. Closes #2105 --- CHANGELOG.md | 12 +++ .../SysManager.Tests/ArchitectureTests.cs | 84 +++++++++++++++++++ SysManager/SysManager/SysManager.csproj | 6 +- .../SysManager/Views/AppUpdatesView.xaml | 12 +-- .../Views/EnvironmentVariablesView.xaml | 5 ++ .../SysManager/Views/WindowsUpdateView.xaml | 8 +- 6 files changed, 114 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 848003bc..6dd7e1e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,18 @@ That paragraph is not decoration: the release workflow copies each entry verbati the GitHub release body and the announcement discussion, so it is the first thing a prospective user reads. CI fails a pull request whose newest entry is missing it. +## [1.76.14] - 2026-09-04 + +Double-clicking a cell in the App Updates or Windows Update list used to put a cursor in it, as if you could +change what it said. You could type — and the app threw the change away. Those lists are reports now, and +behave like it. + +### Fixed +- **Report cells no longer pretend to be editable.** Ten cells across App Updates and Windows Update accepted + typing that went nowhere. One of them was the app's internal Id for a package, which is what gets handed to + Windows' installer — retyping it turned a row that would have updated into a row that reports an error, for + no reason the user could see. The tick boxes for choosing rows are untouched and still work. + ## [1.76.13] - 2026-09-04 Five of the slowest screens never showed the thin progress line under their name in the left-hand list, so if diff --git a/SysManager/SysManager.Tests/ArchitectureTests.cs b/SysManager/SysManager.Tests/ArchitectureTests.cs index 53141c0a..48b07228 100644 --- a/SysManager/SysManager.Tests/ArchitectureTests.cs +++ b/SysManager/SysManager.Tests/ArchitectureTests.cs @@ -5470,6 +5470,90 @@ public void TheTimerResolutionQuery_BindsItsOutParametersCoarsestFinestCurrent() private static partial Regex TimerQueryCall(); + /// + /// A text column in a report grid must not accept typing. + /// + /// + /// Ten cells across App Updates and Windows Update entered edit mode on a double-click. Typing there + /// changed the in-memory row and nothing else, so the app appeared to accept an edit it silently discarded + /// — and one of them was App Updates' Id, the value WingetService.UpgradeAsync builds + /// winget upgrade --id "…" from, which turns a working row into an error row. + /// Per-column, not grid-level. Both grids carry a DataGridCheckBoxColumn for row selection, + /// and DataGrid.IsReadOnly="True" renders those checkboxes untickable — it would break "Upgrade + /// selected" outright. The 20 views that DO set it grid-wide have no checkbox column, which is why they + /// can. Reading the omission as forgetfulness and setting it globally would have been the wrong fix. + /// Nothing reaches a command line through an edited cell: WingetId.IsValid rejects the value + /// and AppUpdatesViewModel catches the ArgumentException per row. This is a UI-honesty rule, + /// not a security one. + /// + [Fact] + public void EveryReportTextColumn_IsReadOnly() + { + // view -> the column Header allowed to be editable, and why. + var editors = new Dictionary(StringComparer.Ordinal) + { + ["EnvironmentVariablesView.xaml"] = + "Value — this tab is an editor, not a report: Apply / Discard / Restore backup sit beside the " + + "grid and UpdateSourceTrigger=PropertyChanged carries each keystroke to the view-model", + }; + + var viewsDir = Path.Combine(FindAppProjectDir(), "Views"); + var columnsChecked = 0; + var typeable = new List(); + + foreach (var file in Directory.GetFiles(viewsDir, "*.xaml")) + { + var name = Path.GetFileName(file); + var text = File.ReadAllText(file); + + // Every grid in the file must be read-only for the grid-level form to count for any column in it. + var grids = DataGridOpeningTag().Matches(text).Select(m => m.Value).ToList(); + if (grids.Count == 0) continue; + var gridReadOnly = grids.TrueForAll(g => g.Contains("IsReadOnly=\"True\"", StringComparison.Ordinal)); + + foreach (var column in ReportTextColumn().Matches(text).Select(m => m.Value)) + { + columnsChecked++; + if (gridReadOnly || column.Contains("IsReadOnly", StringComparison.Ordinal)) continue; + + var header = ColumnHeader().Match(column).Groups["header"].Value; + if (editors.TryGetValue(name, out var allowed) + && allowed.StartsWith(header + " ", StringComparison.Ordinal)) continue; + + typeable.Add($"{name}: {header}"); + } + } + + // Vacuity floor: 125 text columns across the views today, measured with this exact pattern. The + // count matters twice over — a first pass at this used a pattern that also matched + // , a property element rather than a column. There are 43 of those, so + // the population read as 168 and every per-view "typeable cells" number was inflated with it. + Assert.True(columnsChecked >= 118, + $"only {columnsChecked} report text columns were parsed out of 125 measured — the pattern no " + + "longer matches the column shape, so this guard proves nothing."); + + Assert.True(typeable.Count == 0, + "these report cells enter edit mode on a double-click, and the edit goes nowhere. Add " + + "IsReadOnly=\"True\" to the column — NOT to the DataGrid, which would also stop the user " + + "ticking a DataGridCheckBoxColumn. If the cell is genuinely meant to be edited, name it in the " + + "exception list in this test WITH its reason:\n " + string.Join("\n ", typeable)); + } + + /// A DataGrid opening tag. + [GeneratedRegex(@"]*?>", RegexOptions.Singleline)] + private static partial Regex DataGridOpeningTag(); + + /// + /// A DataGridTextColumn element. The negative lookahead keeps + /// <DataGridTextColumn.CellStyle> — a property element, not a column — out of the match. + /// + [GeneratedRegex(@"]*?/?>", RegexOptions.Singleline)] + private static partial Regex ReportTextColumn(); + + /// A column's Header attribute value. + [GeneratedRegex(@"Header=""(?
[^""]*)""")] + private static partial Regex ColumnHeader(); + /// /// A view-model that tracks its own "running" state must forward it to IsBusy. /// diff --git a/SysManager/SysManager/SysManager.csproj b/SysManager/SysManager/SysManager.csproj index 8ae75ce3..2b605436 100644 --- a/SysManager/SysManager/SysManager.csproj +++ b/SysManager/SysManager/SysManager.csproj @@ -10,9 +10,9 @@ SysManager true NU1603;NU1701 - 1.76.13 - 1.76.13.0 - 1.76.13.0 + 1.76.14 + 1.76.14.0 + 1.76.14.0 SysManager SysManager — Windows system monitoring toolkit by laurentiu021. Network, updates, health, logs, safe deep cleanup. https://github.com/laurentiu021/SystemManager diff --git a/SysManager/SysManager/Views/AppUpdatesView.xaml b/SysManager/SysManager/Views/AppUpdatesView.xaml index d3b08a65..bd74cb04 100644 --- a/SysManager/SysManager/Views/AppUpdatesView.xaml +++ b/SysManager/SysManager/Views/AppUpdatesView.xaml @@ -88,12 +88,12 @@ - - - - - - + + + + + + diff --git a/SysManager/SysManager/Views/EnvironmentVariablesView.xaml b/SysManager/SysManager/Views/EnvironmentVariablesView.xaml index 9a163918..71ad1d8b 100644 --- a/SysManager/SysManager/Views/EnvironmentVariablesView.xaml +++ b/SysManager/SysManager/Views/EnvironmentVariablesView.xaml @@ -131,6 +131,11 @@ +