feat(ui): color and acknowledge pinned log errors

Add independent AppLog message types and explicit Class 1/2 severity. Wrap and color log rows, pin Class 1 above Class 2, and let acknowledgement return entries to the current-launch timeline without changing file format or rotation.

Ref: https://logtape.org/manual/levels
This commit is contained in:
2026-11-02 18:17:18 -08:00
parent 01a3fcaaf7
commit a6aea5bf26
23 changed files with 365 additions and 100 deletions
+57 -8
View File
@@ -9,11 +9,15 @@ public partial class MainViewModel
{
private const int VisibleLogEntryLimit = 500;
private readonly ObservableCollection<AppLogEntry> _currentLaunchLogEntries = new();
private readonly ObservableCollection<AppLogEntry> _pinnedLogEntries = new();
private DispatcherTimer? _currentLaunchLogTimer;
private long _lastCurrentLaunchLogSequence;
private bool _isLogsTabSelected;
public ObservableCollection<AppLogEntry> CurrentLaunchLogEntries => _currentLaunchLogEntries;
public ObservableCollection<AppLogEntry> PinnedLogEntries => _pinnedLogEntries;
public bool HasPinnedLogEntries => _pinnedLogEntries.Count > 0;
public RelayCommand<AppLogEntry> AcknowledgeLogEntryCommand => new(AcknowledgeLogEntry);
public bool IsLiveChatTabSelected
{
@@ -41,12 +45,19 @@ public partial class MainViewModel
private void InitializeCurrentLaunchLogs()
{
foreach (var entry in AppLog.SnapshotCurrentLaunch()
.OrderByDescending(entry => entry.Sequence)
.Take(VisibleLogEntryLimit))
_currentLaunchLogEntries.Add(entry);
var entries = AppLog.SnapshotCurrentLaunch()
.OrderByDescending(entry => entry.Sequence)
.Take(VisibleLogEntryLimit)
.ToArray();
foreach (var entry in entries)
{
if (entry.ErrorClass.HasValue)
InsertPinnedLogEntry(entry);
else
_currentLaunchLogEntries.Add(entry);
}
_lastCurrentLaunchLogSequence = _currentLaunchLogEntries.FirstOrDefault()?.Sequence ?? 0;
_lastCurrentLaunchLogSequence = entries.FirstOrDefault()?.Sequence ?? 0;
_currentLaunchLogTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(250) };
_currentLaunchLogTimer.Tick += (_, _) => RefreshCurrentLaunchLogs();
}
@@ -83,13 +94,51 @@ public partial class MainViewModel
foreach (var entry in newEntries)
{
_currentLaunchLogEntries.Insert(0, entry);
if (_currentLaunchLogEntries.Count > VisibleLogEntryLimit)
_currentLaunchLogEntries.RemoveAt(_currentLaunchLogEntries.Count - 1);
if (entry.ErrorClass.HasValue)
InsertPinnedLogEntry(entry);
else
InsertChronologicalLogEntry(entry);
_lastCurrentLaunchLogSequence = entry.Sequence;
}
}
private void InsertPinnedLogEntry(AppLogEntry entry)
{
var index = 0;
while (index < _pinnedLogEntries.Count)
{
var current = _pinnedLogEntries[index];
var currentClass = (int)current.ErrorClass!.Value;
var entryClass = (int)entry.ErrorClass!.Value;
if (currentClass < entryClass
|| (currentClass == entryClass && current.Sequence > entry.Sequence))
index++;
else
break;
}
_pinnedLogEntries.Insert(index, entry);
OnPropertyChanged(nameof(HasPinnedLogEntries));
}
private void AcknowledgeLogEntry(AppLogEntry? entry)
{
if (entry?.ErrorClass == null || !_pinnedLogEntries.Remove(entry)) return;
InsertChronologicalLogEntry(entry);
OnPropertyChanged(nameof(HasPinnedLogEntries));
}
private void InsertChronologicalLogEntry(AppLogEntry entry)
{
var index = 0;
while (index < _currentLaunchLogEntries.Count
&& _currentLaunchLogEntries[index].Sequence > entry.Sequence)
index++;
_currentLaunchLogEntries.Insert(index, entry);
if (_currentLaunchLogEntries.Count > VisibleLogEntryLimit)
_currentLaunchLogEntries.RemoveAt(_currentLaunchLogEntries.Count - 1);
}
private void StopCurrentLaunchLogFeed()
=> _currentLaunchLogTimer?.Stop();
}