Skip to content

Phase 2 app lifecycle skeleton#2

Merged
StuartMeeks merged 4 commits into
masterfrom
phase-2-app-skeleton
May 29, 2026
Merged

Phase 2 app lifecycle skeleton#2
StuartMeeks merged 4 commits into
masterfrom
phase-2-app-skeleton

Conversation

@StuartMeeks

@StuartMeeks StuartMeeks commented May 29, 2026

Copy link
Copy Markdown
Owner

Summary

Bootstraps the WinUI 3 head with a load-bearing startup order, a DI container, single-instance handling, and a Mica-clad MainWindow with a custom title bar.

Boot order

  1. VelopackVelopackApp.Build().Run() runs first so it can intercept install / update / uninstall invocations and post-update relaunch.
  2. WinRT COM wrappers initialised.
  3. Single instanceAppInstance.FindOrRegisterForKey("snipdeck"). If we're not the current instance, redirect activation to the primary and exit.
  4. UI startApplication.Start with a DispatcherQueueSynchronizationContext, then new App().

The XAML-generated Main is disabled (EnableDefaultXamlGeneratedMain=false) so the explicit Program.cs runs.

DI container

Bootstrap.Build() synchronously loads AppConfig from the settings store so the snip-store and backup paths are resolved (config value or path-provider default) before the rest of the graph is built. Registered:

  • IPathProviderWindowsPathProvider (paths under %LOCALAPPDATA%\Snipdeck)
  • IClock, IDispatcher, ISettingsStore, ISnipStore, IBackupService
  • AppConfig (initial load)
  • MainWindow

MainWindow

  • Mica backdrop
  • ExtendsContentIntoTitleBar + custom draggable title bar with "Snipdeck"
  • Applies AppConfig.Theme (Light / Dark / System) to the root FrameworkElement

First run

If the snip store is empty on launch, ExamplesSeed.Build() writes a single "Examples" CLI before the window appears.

Second-instance behaviour

AppInstance.Activated is subscribed in the App constructor. When a secondary instance redirects activation here, the existing MainWindow is reactivated via IDispatcher.Enqueue so the marshalling is safe from any thread.

Test plan

  • dotnet test tests/Snipdeck.Core.Tests — 63 passed
  • CI's Windows app-build job compiles the WinUI head under TreatWarningsAsErrors (I can't verify this from Linux)
  • Manual: launch the app — window appears with Mica, draggable custom title bar, theme follows system / configured value
  • Manual: launch a second time — the first window foregrounds, the second exits

🤖 Generated with Claude Code

StuartMeeks and others added 4 commits May 29, 2026 16:12
Explicit Program.cs Main: Velopack hook → ComWrappersSupport.InitializeComWrappers →
single-instance check via Windows App SDK AppInstance.FindOrRegisterForKey
("snipdeck") with activation redirect to the primary → Application.Start with
a DispatcherQueueSynchronizationContext. Disables the XAML-generated Main so
this order is load-bearing.

Bootstrap.cs builds the DI container
(Microsoft.Extensions.DependencyInjection 10.0.8). Settings are loaded
synchronously up front so the snip-store and backup paths can be resolved
(config value or path-provider default) before the rest of the graph is built.

New abstractions in Core: IPathProvider and IDispatcher.

New App-side services:
  - WindowsPathProvider: paths rooted at %LOCALAPPDATA%\Snipdeck.
  - WinUiDispatcher: lazy-captures the UI thread's DispatcherQueue on first
    use; App constructor warms it up so the secondary-instance activation
    handler can marshal back to the UI thread safely.

MainWindow:
  - Mica backdrop (already on the scaffolded XAML).
  - ExtendsContentIntoTitleBar + custom draggable title bar showing the app
    name.
  - Theme applied from AppConfig (Light / Dark / System).

App.xaml.cs:
  - Owns the static IServiceProvider.
  - Runs ExamplesSeed.Build() into the snip store on first run (empty store).
  - Subscribes to AppInstance.Activated; a redirect from a secondary instance
    foregrounds the existing main window.

Core tests still pass (63/63).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EnableDefaultXamlGeneratedMain=false alone doesn't propagate to the WinUI
XAML compiler in WindowsAppSDK 2.1.0 — it still emits the Program class with
its own Main, colliding with Program.cs. The compiler wraps that Program in
#if !DISABLE_XAML_GENERATED_MAIN, so define that constant explicitly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…itions

Program.cs used '_' as the lambda parameter, which made '_ = new App()'
inside the lambda assign App to the parameter (typed as
ApplicationInitializationCallbackParams) instead of being a discard. Rename
the parameter to 'p' so the discard works as intended.

TODO.md is a new backlog file. Seeded with the shared-parameter-definitions
idea: today every Snip carries its parameter definitions inline (env, region,
etc.), and the same definition gets duplicated across many Snips. Sketches a
shared-by-name model at both CLI and global scope, with local overrides on
the Snip, and notes the open design questions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- AllowUnsafeBlocks=true: CsWinRT1030 requires it so the generators can emit
  the marshalling glue when generic interface types (IServiceProvider,
  ServiceProvider) cross the WinRT ABI.
- Bootstrap: chain the AddSingleton calls to consume the IServiceCollection
  return value (IDE0058) — clean fluent style, also fewer lines.
- MainWindow.ApplyTheme: list ThemePreference.System explicitly so the switch
  is exhaustive (IDE0072) and keep the underscore arm for defence in depth.
- WinUiDispatcher: lazy-init via a GetQueue() method instead of a Queue
  property; the analyser misreads the '??=' property as 'use auto-property'
  (IDE0032). A method sidesteps it without changing the semantics.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@StuartMeeks StuartMeeks merged commit 0b64eb1 into master May 29, 2026
2 checks passed
@StuartMeeks StuartMeeks deleted the phase-2-app-skeleton branch May 29, 2026 16:29
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