refactor: log silenced UI errors, fix timer leak, narrow exception catch

- EventListShell: detach the Elapsed handler before disposing the refresh timer
  (both StartTimer and Dispose) to stop a leaked subscription firing on a
  torn-down component; log the two previously-silent catches.
- Insights: log the previously-silent report-load catch.
- EventOddsParser: narrow catch(Exception) to catch(ArgumentException) so only
  the OddsRate/OddsValue/Bet guard-clause throws are swallowed.
- AnomalyEvidenceData: make the JSON DTOs init-only per the immutability convention.
- Settings: remove a dead DialogParameters block.
This commit is contained in:
2026-05-28 22:34:17 +03:00
parent f294255f10
commit 0501f9c39c
5 changed files with 47 additions and 39 deletions
@@ -15,6 +15,7 @@
@inject IStringLocalizer<SharedResource> L
@inject IAnomalyInsightsService InsightsService
@inject NavigationManager Nav
@inject ILogger<Insights> Logger
<PageTitle>@L["App.Title"] · @L["Nav.Insights"]</PageTitle>
@@ -658,8 +659,9 @@
_vm = report;
}
catch (OperationCanceledException) { /* superseded */ }
catch
catch (Exception ex)
{
Logger.LogError(ex, "Insights: failed to build the anomaly outcome report.");
_errored = true;
_vm = null;
}
-7
View File
@@ -282,13 +282,6 @@
private async Task<bool> ConfirmAsync()
{
var parameters = new DialogParameters
{
["ContentText"] = L["Settings.Confirm.Body"].Value,
["ButtonText"] = L["Settings.Action.Save"].Value,
["CancelText"] = L["Common.Cancel"].Value,
};
var result = await Dialogs.ShowMessageBox(
title: L["Settings.Confirm.Title"],
message: L["Settings.Confirm.Body"],
@@ -14,6 +14,7 @@
@using DomainEventId = Marathon.Domain.ValueObjects.EventId
@implements IDisposable
@inject IStringLocalizer<SharedResource> L
@inject ILogger<EventListShell> Logger
<section class="m-shell">
<header class="m-rise m-rise-1" style="display: grid; gap: var(--m-space-3); max-width: 880px;">
@@ -364,7 +365,11 @@
private void StartTimer()
{
_refreshTimer?.Dispose();
if (_refreshTimer is not null)
{
_refreshTimer.Elapsed -= OnRefreshTimerElapsed;
_refreshTimer.Dispose();
}
var interval = Math.Max(5, AutoRefreshSeconds) * 1000.0;
_refreshTimer = new System.Timers.Timer(interval) { AutoReset = true };
_refreshTimer.Elapsed += OnRefreshTimerElapsed;
@@ -383,10 +388,11 @@
{
await InvokeAsync(LoadAsync);
}
catch
catch (Exception ex)
{
// Swallowed — LoadAsync already handles its own errors; this catch
// is the last line of defense for InvokeAsync itself.
// Last line of defense for InvokeAsync itself — LoadAsync handles its
// own errors. Log rather than silently dropping the failure.
Logger.LogError(ex, "EventListShell ({Surface}): auto-refresh tick failed.", Surface);
}
}
@@ -414,9 +420,10 @@
{
// Swallow — superseded by a newer load.
}
catch
catch (Exception ex)
{
// Hide errors from the UI; Phase 9 will add a snackbar.
// Degrade gracefully (clear the rows) but record the failure for diagnosis.
Logger.LogError(ex, "EventListShell ({Surface}): failed to load event rows.", Surface);
_rows = new List<EventListItem>();
}
finally
@@ -523,7 +530,11 @@
public void Dispose()
{
_refreshTimer?.Dispose();
if (_refreshTimer is not null)
{
_refreshTimer.Elapsed -= OnRefreshTimerElapsed;
_refreshTimer.Dispose();
}
_searchCts?.Cancel();
_searchCts?.Dispose();
_loadCts?.Cancel();