Skip to content

feat(ui): route command exceptions through report+log pipe - #147

Merged
mrcsin merged 1 commit into
masterfrom
report-log-extension
Jul 27, 2026
Merged

feat(ui): route command exceptions through report+log pipe#147
mrcsin merged 1 commit into
masterfrom
report-log-extension

Conversation

@mrcsin

@mrcsin mrcsin commented Jul 27, 2026

Copy link
Copy Markdown
Member

Problem

Command-exception handling was eight copies of the same
ThrownExceptions → ObserveOn → panel.ReportError block, and none of them
logged. 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 MainWindowViewModel subscriptions had no
guard (a throw in the callback escaped to the dispatcher, fatal for the
per-second sync-time tick), and GridStyleEditorViewModel.SaveCommand had no
ThrownExceptions handler at all — an unexpected save fault crashed the process.

What changed

  • Extract one ReportThrownExceptions(command, panel, logger, context) extension
    that 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 redundant ObserveOn
    is dropped (the panel self-marshals via PostOnUiThread).
  • Guard the three bare MainWindowViewModel subscriptions so a throw in the
    callback body is reported instead of escaping.
  • Wire GridStyleEditorViewModel.SaveCommand faults to the editor's own
    ErrorMessage (a modal fault must not land behind the modal) plus the log.
  • No IMessageSink interface: MessagePanelViewModel has one implementation, is
    not mocked, and crosses no layer boundary, so consumers keep the concrete panel.
    The one interface introduced — IGridStyleEditorFacade — is warranted (it is now
    mocked 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 format clean; Rider analyzer clean on all changed files.
  • New regression tests reproduce each closed fault: a throwing command reports and logs the exception; SaveCommand.Execute() through a throwing facade surfaces on ErrorMessage + log without crashing; a throw inside a guarded onNext body is contained.
  • Ran through the full multi-agent review chain (comprehensive, smells, comment audit, critical); two MAJOR findings caught and fixed (subscription guards only caught source errors, not callback-body throws; SaveCommand wiring was tested only at the seam).
Per-task commits before collapse
1701f5d feat(ui): add report+log command-exception extension
b71f7eb feat(ui): route ClipboardViewModel command errors through report+log extension
b28fe4f feat(ui): route RecipeFileViewModel command errors through report+log extension
3b4da06 feat(ui): guard MainWindowViewModel command and subscription errors
f1e683b feat(ui): surface and log GridStyleEditor save faults on its own error surface
6fa2e20 docs(architecture): document report+log command-exception pipe
92d0580 fix(ui): address review findings — guard onNext throws, test wiring, log/doc fixes
94a046e refactor(ui): use shared test logger and IGridStyleEditorFacade seam
67d1937 refactor(ui): drop redundant doc comment on ExceptionReporter
2461a5b docs(plans): record exec branch + verification steps for PR-F1

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
mrcsin marked this pull request as ready for review July 27, 2026 14:16
@mrcsin
mrcsin merged commit 794767f into master Jul 27, 2026
1 check passed
@mrcsin
mrcsin deleted the report-log-extension branch July 27, 2026 14:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant