Track AI native memory in subsystem metrics
Details
AI services (ONNX, LLamaSharp, Whisper.net) allocate model weights through
their own native libraries, bypassing both the managed heap and the Rust
allocator. This made their memory invisible to SubsystemTracker — the
Dashboard showed "—" for Intent Engine, Local LLM, and Speech-to-Text, and
only managed-side allocations for Embeddings/RAG.
SubsystemTracker: add ReportNativeBytes(subsystemId, bytes) and static
convenience method. Allows C# services to report native memory directly,
complementing the existing Rust FFI path (MergeNativeCounters).
EmbeddingService: report ONNX model file size as native memory on load,
clear on unload/dispose.
LocalLlamaProvider: report GGUF model file size as native memory on load,
clear on unload/dispose and when swapping models in EnsureModelLoadedAsync.
WhisperService: report Whisper GGML model file size as native memory on
load, clear on re-init/dispose.
File size is used as the estimate since these libraries memory-map or load
the full model file into native memory. The actual resident footprint may
differ slightly (runtime buffers, KV cache) but file size provides the
right order of magnitude and tracks load/unload transitions accurately.
Shell resource & disposal refactor — Phase 1
Details
Desktop ViewModelBase was a 7-line stub with no disposal support, causing
accumulated event handlers, leaked plugin VMs, and inflated subsystem metrics.
ViewModelBase: Mirror SDK pattern — add IDisposable + CompositeDisposable with
Dispose(bool) guard. All 23 desktop VMs now gain Disposables for subscription
cleanup. SyncPairingViewModel's Dispose() updated to override base properly.
SubsystemTracker: Fix rendering metric from cumulative to snapshot. The
Interlocked.Add on UI thread allocation delta caused ManagedAllocBytes to grow
monotonically (950 MB after 5 min idle). Switch to Interlocked.Exchange to
track per-tick delta, matching the pattern already used for runtime.gc.
MainWindow event leaks:
- WireBalloonPositioning: store handler reference as field, unsubscribe
previous before subscribing new (was adding anonymous lambda on every
DataContext change)
- SetupSpeechRecording: track _speechWiredVm, unsubscribe old VM's
TranscriptionReady before subscribing new (was double-subscribing from
constructor + DataContextChanged)
- OnClosing: unsubscribe all window event handlers (Opened, PositionChanged,
PropertyChanged, TitleBarSpacer.PointerPressed), balloon/speech handlers,
dispose _chordTimer, unsubscribe OnMainVmPropertyChanged from VM
MainWindowViewModel:
- EvictPluginCache/ReloadPluginAsync: call Dispose() on removed VMs
- OnWorkspaceChanged: dispose all cached VMs before Clear()
- Cleanup: dispose all cached VMs, unsubscribe SettingsVM event handlers
- SettingsVM: replace anonymous lambdas with named methods
(OnSettingsSwitchWorkspaceRequested, OnSettingsLogoutRequested)
Add floating Duncan FAB in lower-right content area
Details
Add a 48x48 circular floating action button (FAB) in the lower-right
corner of the content area panel. Uses the same star glyph as the
sidebar AI icon at 28x28, white on ThemePrimaryBrush background
with drop shadow. Includes gold dot indicator for unseen insights.
Relocate the AI speech balloon from bottom-left (over sidebar star)
to bottom-right, originating above the FAB. Arrow tail flipped to
right-aligned. PositionBalloonOverStar renamed to PositionBalloonOverFab
with right-margin calculation relative to the FAB center.
Sidebar AI star icon remains as a secondary access point.
Get notified about new releases