feat(ui): route command exceptions through report+log pipe - #147
Merged
Conversation
Command-exception handling was eight copies of the same
ThrownExceptions -> ObserveOn -> panel.ReportError block, and none of
them logged: the user saw the message but the exception type and stack
never reached the log, leaving field-reported errors undiagnosable.
Extract one ReportThrownExceptions(command, panel, logger, context)
extension that both reports to the message panel and logs the exception
with its stack via the caller's ILogger<T>, so the log line carries the
originating class through {SourceContext}. Route all eight sites through
it and drop the redundant ObserveOn, since the panel self-marshals.
Close the failure paths the review surfaced along the way: guard the
three bare MainWindowViewModel subscriptions so a throw in the callback
body is reported instead of escaping to the dispatcher, and wire
GridStyleEditorViewModel.SaveCommand faults to the editor's own
ErrorMessage plus the log. That command had no ThrownExceptions handler
and crashed the process on an unexpected save error.
No IMessageSink interface: MessagePanelViewModel has one implementation,
is not mocked, and crosses no layer boundary, so consumers keep the
concrete panel. No happy-path behaviour change; on failure the same
panel message appears and a stack-carrying log line is added.
mrcsin
marked this pull request as ready for review
July 27, 2026 14:16
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Command-exception handling was eight copies of the same
ThrownExceptions → ObserveOn → panel.ReportErrorblock, and none of themlogged. A user saw the panel message, but the exception type and stack never
reached the log — a field-reported error left nothing to diagnose with. Two
paths were worse than silent: three
MainWindowViewModelsubscriptions had noguard (a throw in the callback escaped to the dispatcher, fatal for the
per-second sync-time tick), and
GridStyleEditorViewModel.SaveCommandhad noThrownExceptionshandler at all — an unexpected save fault crashed the process.What changed
ReportThrownExceptions(command, panel, logger, context)extensionthat reports to the panel and logs the exception with its stack via the
caller's
ILogger<T>, so the log line carries the originating class through{SourceContext}. All eight sites route through it; the redundantObserveOnis dropped (the panel self-marshals via
PostOnUiThread).MainWindowViewModelsubscriptions so a throw in thecallback body is reported instead of escaping.
GridStyleEditorViewModel.SaveCommandfaults to the editor's ownErrorMessage(a modal fault must not land behind the modal) plus the log.IMessageSinkinterface:MessagePanelViewModelhas one implementation, isnot mocked, and crosses no layer boundary, so consumers keep the concrete panel.
The one interface introduced —
IGridStyleEditorFacade— is warranted (it is nowmocked in a test and crosses the Core→UI boundary).
No happy-path behaviour change: on success nothing differs; on failure the same
panel message appears and a stack-carrying log line is added.
This is PR-F1 of the error/logging pipe roadmap. Follow-ons: PR-F2 (global
exception backstop), then #113 rebased on both.
Verification
dotnet build SemiStep.slnx— 0 warnings (warnings are errors).dotnet test SemiStep/SemiStep.Tests/SemiStep.Tests.csproj— 1474 passed, 0 failed (+4 new tests).dotnet formatclean; Rider analyzer clean on all changed files.SaveCommand.Execute()through a throwing facade surfaces onErrorMessage+ log without crashing; a throw inside a guardedonNextbody is contained.Per-task commits before collapse