Skip to content

Unhandled exception paths in UI commands: hotkey Subscribe without onError, RecipeCommandsViewModel lacks ThrownExceptions, fire-and-forget conflict dialog #113

Description

@mrcsin

Summary

Several UI command paths let exceptions escape unobserved or crash the app, deviating from the pattern the rest of the codebase follows (ClipboardViewModel, RecipeFileViewModel, MainWindowViewModel all subscribe ThrownExceptions).

1. Keyboard shortcuts crash the app on any command exception

MainWindow.axaml.cs:87-105 — all four OnKeyDown paths use Execute().Subscribe() with no onError. A ReactiveCommand execution exception errors the Execute() observable, and the parameterless Subscribe rethrows it unhandled on the UI thread — even for commands whose ThrownExceptions are otherwise handled.

2. RecipeCommandsViewModel subscribes ThrownExceptions nowhere

RecipeGrid/RecipeCommandsViewModel.cs:38-46AddStepCommand/DeleteStepCommand/UndoCommand/RedoCommand have no ThrownExceptions handler, and AddStep reaches RecipeCoordinator.GetDefaultActionId() (RecipeCoordinator.cs:168-172) which throws InvalidOperationException. Combined with finding 1: a hotkey → unhandled crash.

Also (RecipeCommandsViewModel.cs:70-100): AddStep/DeleteStep check result.IsSuccess only to drive selection; a failed Result is dropped without a message-panel report, unlike every other VM.

3. Fire-and-forget conflict dialog swallows exceptions

MainWindow/MainWindowViewModel.cs:83_ = HandleConflictAsync(conflict.Local, conflict.Plc) discards the task. Inside, only ShowDialog is guarded (212-222); an exception from _coordinator.ResolveConflict or MessagePanel.ReportFailure faults the discarded task and vanishes as an unobserved exception. The codebase already has the right pattern: PlcLifecycleManager.cs:279-281 uses .ContinueWith(t => log, OnlyOnFaulted).

4. Unguarded async void around dialogs

StyleEditor/GridStyleEditorWindow.axaml.cs:30-40OnSaveCompleted is async void invoked from an Rx Subscribe callback (not an event handler); any exception from ShowDialog/Close crashes the process. MainWindow.axaml.cs:206-243OnWindowClosing is a legitimate async void event handler but has no try/catch around its awaits.

Suggested fix

  • Add ThrownExceptions subscriptions in RecipeCommandsViewModel reporting to the message panel; report failed Results there too.
  • Pass an onError at the hotkey call sites (or a small SafeExecute extension used everywhere a command is invoked imperatively).
  • Wrap HandleConflictAsync body in try/catch-log, or use the ContinueWith(OnlyOnFaulted) pattern.
  • Pump OnSaveCompleted through the Rx pipeline (SelectMany) or add try/catch; same for OnWindowClosing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions