fix(client-ui): resolve Low code-review findings (Client.UI-003,004,006,009,010,011)

- Client.UI-003: wire Serilog properly per CLAUDE.md — console sink +
  rolling daily file sink in Program.Main, Log.CloseAndFlush in finally,
  per-VM Log.ForContext<> loggers.
- Client.UI-004: migrate the cert-store folder picker from the obsolete
  OpenFolderDialog to StorageProvider.OpenFolderPickerAsync (with
  TryGetFolderFromPathAsync seed + TryGetLocalPath extraction).
- Client.UI-006: surface formerly silent catch blocks via an observable
  StatusMessage on the Subscriptions / Alarms VMs that bubbles up into
  the shell's status bar; soft fallbacks log at Information level so
  hard failures stay distinguishable.
- Client.UI-009: docs/Client.UI.md now lists Standard Deviation in the
  Aggregate row of the Query Options table.
- Client.UI-010: removed the unused MinDateTimeProperty /
  MaxDateTimeProperty styled properties from DateTimeRangePicker.
- Client.UI-011: updated the cert-store TextBox watermark from the
  legacy AppData/LmxOpcUaClient/pki to the canonical
  AppData/OtOpcUaClient/pki.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-05-23 11:25:20 -04:00
parent 59ecd18169
commit 1b10194634
14 changed files with 246 additions and 64 deletions
@@ -2,6 +2,7 @@ using System.ComponentModel;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Platform.Storage;
using SkiaSharp;
using Svg.Skia;
using ZB.MOM.WW.OtOpcUa.Client.UI.ViewModels;
@@ -126,15 +127,34 @@ public partial class MainWindow : Window
{
if (DataContext is not MainWindowViewModel vm) return;
var dialog = new OpenFolderDialog
var topLevel = TopLevel.GetTopLevel(this);
if (topLevel == null) return;
IStorageFolder? startLocation = null;
if (!string.IsNullOrEmpty(vm.CertificateStorePath))
{
try
{
startLocation = await topLevel.StorageProvider.TryGetFolderFromPathAsync(vm.CertificateStorePath);
}
catch
{
// Best-effort: if the existing path can't be resolved (missing/permission), open the dialog without it.
}
}
var folders = await topLevel.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
Title = "Select Certificate Store Folder",
Directory = vm.CertificateStorePath
};
AllowMultiple = false,
SuggestedStartLocation = startLocation
});
var result = await dialog.ShowAsync(this);
if (!string.IsNullOrEmpty(result))
vm.CertificateStorePath = result;
if (folders.Count == 0) return;
var picked = folders[0].TryGetLocalPath();
if (!string.IsNullOrEmpty(picked))
vm.CertificateStorePath = picked;
}
protected override void OnClosing(WindowClosingEventArgs e)