Skip to main content

Changelog

Every improvement, automatically tracked from our commit history.

Subscribe via Atom feed
← Prev Page 12 of 212 Next →
February 28, 2026
patch Desktop ShellServerServices

Unload AI models from memory when AI is disabled

Server 1.68.6 | Desktop 1.68.6 | Services 1.68.6 | 9fab8e71
Details

When the user toggles AI off in settings, the ONNX embedding model

(InferenceSession + tokenizer) and local LLM weights (LLamaWeights +

LLamaContext) were staying resident in memory because nothing called

Dispose or released the native resources. This caused ~1.7GB+ of

memory to persist even after AI was disabled.

Changes:

  • Add EmbeddingService.UnloadAsync() — disposes the ONNX session and

tokenizer without permanently disposing the service, so it can be

re-initialized later via InitializeAsync()

  • Add LocalLlamaProvider.UnloadModelAsync() — same pattern for the

LLama weights and context

  • Add IEmbeddingService.UnloadAsync() to the interface contract

(+ HeadlessEmbeddingService no-op stub)

  • Wire OnAiEnabledChanged(false) to call UnloadAiModelsAsync() which

releases both models and nudges GC to reclaim native buffers

  • Add RagIndexService + EmbeddingService disposal to

MainWindowViewModel.Cleanup() for clean app shutdown

patch Desktop ShellServices

Fix Dashboard blocked by slow API health check (5-min timeout)

Desktop 1.68.6 | Services 1.68.6 | 146e4e31
Details

Root cause: RefreshAsync populated AllPlugins AFTER awaiting

IsOnlineAsync(), which used PluginInstallService's 5-minute timeout

HttpClient for a simple health check. When privstack.io was slow or

unreachable, the entire dashboard sat on "Loading..." for minutes

with nothing rendered.

Fix: Split RefreshAsync into three phases:

1. PopulateLocalPlugins() — instant, no network. Builds AllPlugins

from filesystem manifests + IPluginRegistry.Plugins (the actually

loaded plugins). Sets IsLoading=false immediately so plugins render.

2. LoadSystemMetricsAsync() — local metrics (disk sizes, memory).

3. TryFetchRemoteRegistryAsync() — non-blocking enrichment with

server data. If it fails, local plugins are already displayed.

Also added a 5-second CancellationToken timeout to IsOnlineAsync()

so the health check doesn't inherit the 5-minute download timeout.

patch Desktop Shell

Fix Dashboard showing no plugins when registry is offline

Desktop 1.68.6 | 446e7a4c
Details

The previous fix handled the API timeout gracefully but the dashboard

still showed 0 plugins because GetInstalledVersions() only scans

~/.privstack/plugins and bundled plugin directories for manifest.json

files. In dev mode (project references) and when plugins are loaded

from non-standard paths, no manifests exist in those directories.

Added a fallback that populates AllPlugins from IPluginRegistry.Plugins

— the actually loaded/running plugins — for any plugin not already

covered by the server registry or filesystem manifests. This ensures

the dashboard always shows all active plugins with their metadata

regardless of how they were loaded.

patch Desktop ShellServices

Fix Dashboard showing empty when plugin registry API times out

Desktop 1.68.6 | Services 1.68.6 | b964b411
Details

The Dashboard RefreshAsync method called AllPlugins.Clear() before

fetching the remote plugin registry, so when the API call timed out

the entire catch block skipped populating locally installed plugins,

system metrics, and workspace activation state — leaving the UI stuck

showing "0 installed · 0 available" with empty metric cards.

Restructured RefreshAsync to:

  • Fetch local installed versions first (never fails)
  • Wrap the remote registry fetch in its own try/catch so timeouts

are non-fatal — locally installed plugins still appear

  • Always load system metrics regardless of registry availability

Also increased PrivStackApiClient timeout from 15s to 30s since the

plugin registry endpoint can be slow.

patch Services

Remove N+1 individual entity reads from BacklinkService index build

Services 1.68.6 | 1a5400b6
Details

Phase 2 of BuildIndexAsync already calls WikiLinkParser.ExtractContentFromEntity

on each entity from ReadList. The "load individually" block re-fetched entities

without content via individual SdkAction.Read calls, but the individual Read

returns the same JSON shape — ExtractContentFromEntity produces the same empty

result. This caused 21+ unnecessary contact.Read SDK calls at startup.

Removing the block eliminates the N+1 query pattern with no behavioral change,

since entities that genuinely lack parseable text fields won't gain content from

a re-fetch.

← Prev Page 12 of 212 Next →

Get notified about new releases