From 4d93c6a20ea5ae343c15740cc7fbe15bf8d3e6da Mon Sep 17 00:00:00 2001 From: Stuart Meeks Date: Sat, 30 May 2026 00:29:33 +0000 Subject: [PATCH] Fix startup crash by keeping ShellViewModel.LoadAsync on the UI thread MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LoadAsync used ConfigureAwait(false), so after the store load the continuation resumed on a thread-pool thread and RebuildCliChoices() mutated CliChoices — an ObservableCollection already bound to the CLI switcher — off the UI thread. WinRT rejects the cross-thread collection-changed marshal with RPC_E_WRONG_THREAD (HRESULT 0x8001010E), crashing the app at first launch. Switch to ConfigureAwait(true) and document why. Co-Authored-By: Claude Opus 4.7 --- CHANGELOG.md | 7 +++++++ src/Snipdeck.Core/ViewModels/ShellViewModel.cs | 5 ++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dad9efa..290c529 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed +- First-run crash on startup (`RPC_E_WRONG_THREAD` / `0x8001010E`): + `ShellViewModel.LoadAsync` resumed on a thread-pool thread after loading + the store and then mutated `CliChoices`, an `ObservableCollection` already + bound to the CLI switcher. WinRT rejects cross-thread collection-changed + marshalling. The await now stays on the UI thread. + ### Added — Phase 6: Settings page + Velopack updater - **Settings page becomes editable.** Theme switches live (System/Light/Dark apply immediately via `IThemeApplier` → `MainWindow`'s content tree). Close diff --git a/src/Snipdeck.Core/ViewModels/ShellViewModel.cs b/src/Snipdeck.Core/ViewModels/ShellViewModel.cs index 70f408b..a07faa1 100644 --- a/src/Snipdeck.Core/ViewModels/ShellViewModel.cs +++ b/src/Snipdeck.Core/ViewModels/ShellViewModel.cs @@ -66,7 +66,10 @@ public ShellViewModel( public async Task LoadAsync(CancellationToken cancellationToken = default) { - _document = await _store.LoadAsync(cancellationToken).ConfigureAwait(false); + // Stay on the UI thread after the await — RebuildCliChoices mutates + // an ObservableCollection that XAML is already bound to, and WinRT + // collection-change marshalling requires the original thread. + _document = await _store.LoadAsync(cancellationToken).ConfigureAwait(true); RebuildCliChoices(); SelectedCliChoice = CliChoices.FirstOrDefault(); }