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 2e69b976..bc014ed7 100644 --- a/SysManager/SysManager.Tests/ArchitectureTests.cs +++ b/SysManager/SysManager.Tests/ArchitectureTests.cs @@ -5496,6 +5496,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 @@ +