Skip to main content

Changelog

Every improvement, automatically tracked from our commit history.

Subscribe via Atom feed
← Prev Page 15 of 139 Next →
February 28, 2026
patch Desktop Shell

Parallelize plugin init and Dashboard metrics for client mode perf

Details

In client mode, every SDK call is an HTTP roundtrip (~100-300ms each).

Sequential execution of 30-45 calls during startup and 20-30 calls for

Dashboard metrics caused 5-10 second load times.

Two parallelization changes:

1. Plugin initialization: Split DiscoverAndInitialize into two phases.

Phase 1 (serial): vault unlock, host creation, schema registration

— these are fast and may touch shared state. Phase 2 (parallel):

InitializeAsync on all plugins concurrently via Task.WhenAll — each

plugin is independent and the SdkHost's ReaderWriterLockSlim allows

concurrent reads.

2. Dashboard metrics: SystemMetricsService.GetDataMetricsAsync now

fetches all IDataMetricsProvider results via Task.WhenAll instead of

sequential foreach+await. This turns 11 sequential plugin queries

into parallel HTTP calls.

Both changes are safe for standalone mode (FFI calls are fast, so the

parallelism just adds minor thread pool overhead with no UX impact).

patch Desktop ShellServices

Fix greyed-out sidebar in client mode (concurrent command execution)

Details

CommunityToolkit's AsyncRelayCommand disables the command while it's

executing, preventing concurrent calls. In client mode, SelectTab awaits

plugin.OnNavigatedToAsync() which makes HTTP SDK calls to the server —

these are much slower than local FFI calls. While the first SelectTab

is waiting on the HTTP response, all sidebar buttons bound to the same

SelectTabCommand are disabled (greyed out) because CanExecute returns

false during execution.

Fix: AllowConcurrentExecutions = true on SelectTab, matching the

expected behavior where rapid tab switching should always work.

Also fix HttpSdkTransport stale logger (same pattern as PluginRegistry:

static readonly ForContext becomes dead after ReconfigureForWorkspace).

patch Desktop Shell

Fix plugin assembly resolution for cross-project dependencies

Details

PluginLoadContext now resolves assemblies from sibling plugin directories

when the plugin's own directory and host don't have them. This fixes the

Tasks plugin failing to load in client mode with FileNotFoundException

for PrivStack.Plugin.Tasks.Headless — the Headless DLL is deployed to

its own sibling directory (plugins/PrivStack.Plugin.Tasks.Headless/)

and the Tasks plugin depends on it for shared models and services.

Also guard Emergency Kit regeneration in client mode — SetupRecovery()

requires the native runtime which isn't initialized when running as a

client. Shows a clear message directing users to the server instance.

patch Desktop Shell

Fix client mode: no plugin UI and silent logging after workspace switch

Details

Two root causes fixed:

1. PluginRegistry logging silently dead after workspace switch:

The static readonly ILogger field was captured from the old Serilog logger.

When ReconfigureForWorkspace calls Log.CloseAndFlush(), the ForContext child

becomes bound to a disposed logger — all subsequent messages silently vanish.

Changed to a property that always reads from the current Serilog.Log instance.

Added Console.Error fallback in plugin init catch blocks so errors are always

visible regardless of Serilog state.

2. No UI for any plugin in client mode:

MainWindowViewModel.SelectTab() guards on _service.IsInitialized, where

_service is IPrivStackRuntime (native Rust/DuckDB runtime). In client mode

the native runtime is intentionally NOT initialized — the headless server

owns DuckDB and the desktop routes all SDK calls through HTTP transport.

This caused SelectTab to abort with "Not initialized" for every plugin,

preventing any plugin view from being created or displayed.

Added IsServiceReady property that checks either native init OR client mode,

and replaced all 4 guard sites in the ViewModel.

patch Desktop Shell

Fix client mode runtime issues: Tasks plugin, API server, and warnings

Details
  • PluginRegistry: Skip *.Headless.dll during assembly scan in Desktop mode.

Headless plugin DLLs are server-only but were being loaded by the Desktop's

ScanAssemblyPlugins, creating type identity conflicts and duplicate plugin

IDs (both TasksPlugin and TasksHeadlessPlugin share ID "privstack.tasks").

  • SettingsViewModel: Guard ToggleApiServerAsync with App.IsClientMode.

Setting ApiEnabled in the constructor triggered OnApiEnabledChanged which

called StartAsync on the local API server — even in client mode where

the remote server already owns port 9720.

  • MainWindowViewModel: Skip SubscriptionValidationService.ValidateAsync

in client mode. The service calls ILicensingService.GetLicenseStatus()

which hits the uninitialized native runtime. License status is already

proxied from the server during client mode detection.

  • SyncPairingViewModel: Skip LoadInitialState in client mode. The method

calls IPairingService FFI methods (GetSyncCode, SetDeviceName, etc.)

that require the native runtime. Server handles sync in client mode.

← Prev Page 15 of 139 Next →

Get notified about new releases