Remove bottom status bar, move AI + storage state into sidebar
Details
Delete the bottom status bar (~30px) to give plugin content full window
height. The bar contained: status message (removed — no value), active
timer indicator (already in sidebar footer), update status (in Dashboard),
subscription badge (in Settings > About), AI persona button (moved to
sidebar), and storage state pill (moved to sidebar Sync row).
Sidebar expanded footer gains:
- AI Persona button (star icon + gold dot for unseen insights) between
Search and Sync rows
- Storage state label ("Local"/"Cloud") appended to the Sync row
Sidebar collapsed footer gains:
- AI Persona icon with gold dot indicator
- Sync tooltip now includes storage state text
AI balloon repositioned from bottom-right to bottom-left to align with
the sidebar star icon. Balloon positioning code updated to search the
visual tree (star icon is now in NavigationSidebar's name scope).
Removed unused timer-pulse animation style from MainWindow.
Version: 1.69.0 → 1.70.0
Add per-subsystem memory tracker and Dashboard Subsystems tab
Details
Adds visibility into which subsystems consume memory across .NET managed
heap, native Rust allocations, and background task activity. Enables
live per-subsystem metrics in the Dashboard.
Rust tracking allocator (Phase 1):
- New allocator.rs: custom GlobalAlloc with 16-byte header per allocation
storing size + subsystem ID. Per-subsystem atomic counters track bytes
and allocation counts. Thread-local subsystem tag set via with_subsystem()
scope guard.
- Subsystem IDs: untagged(0), storage(1), sync(2), crypto(3), cloud(4),
plugins(5), ffi(6), reserved(7)
- New FFI export: privstack_subsystem_memory() returns JSON snapshots
- Key entry points wrapped: privstack_execute (FFI), auth_initialize/unlock
(Crypto), sync_status (Sync)
C# SubsystemTracker (Phase 2):
- SubsystemTracker: singleton service with AsyncLocal tagging, RunTagged()
for async/sync task wrapping, EnterScope() for synchronous code blocks,
5-sample rolling allocation rate, native counter merging from Rust FFI
- SubsystemDefinitions: static table of 16 built-in subsystems across
UI, AI, Core, Services, and Runtime categories
- Static RunTaggedStatic() convenience for instrumentation without DI
Entry point instrumentation (Phase 3):
- RagIndexService: consumer + deferred init tagged as ai.rag
- IntentEngine: signal consumer tagged as ai.intent
- EmbeddingService: ONNX load + tokenizer tagged as ai.embedding
- LocalLlamaProvider: model load tagged as ai.llm
- WhisperService: model load tagged as ai.whisper
- IpcServer: listener tagged as ipc
- ReminderSchedulerService: initial poll tagged as reminders
- FileEventInboundScanner: poll loop tagged as core.sync
- SyncOutboundService: debounce callback tagged as core.sync
- CloudSyncSettingsViewModel: push + status loop tagged as core.cloud
- UpdateViewModel: startup check tagged as updates
Dashboard Subsystems tab (Phase 4):
- New SubsystemItemViewModel with status dot coloring (active/idle/stopped)
- DashboardViewModel extended with Subsystems tab, summary cards (threads,
managed heap, native memory, active count), per-subsystem item collection
- DashboardView.axaml: third tab button, summary stat cards, table-style
subsystem list with status dot, name, category, task count, memory,
alloc rate columns
- Live refresh on 1-second timer when Subsystems tab is active
Registration wiring (Phase 5):
- SubsystemTracker registered as singleton in CoreServiceRegistration
- Static subsystems registered at startup, eagerly resolved in WireCorePostBuild
- Plugin subsystems registered dynamically in DashboardPlugin.CreateViewModelCore()
- NativeLibrary P/Invoke added for privstack_subsystem_memory
Version bumps:
- Rust workspace: 1.15.3 → 1.15.4
- Desktop app: 1.68.6 → 1.69.0
- Dashboard plugin: 1.4.0 → 1.5.0
Fix RAG index cascade on bulk file operations
Details
RagIndexService now uses batch-coalescing debounce instead of per-entity
timers. Incoming EntitySyncedMessages are queued and deduplicated, then
dispatched as a single batch — calling each IIndexableContentProvider at
most once per batch and loading hashes once. This reduces bulk operations
(e.g. trashing 40 files) from ~560 ReadList calls to ~14.
SdkMessage gains a SuppressChangeNotification property (JsonIgnored) that
tells SdkHost to skip the EntitySyncedMessage broadcast while still
recording sync outbound snapshots. Batch callers send a single summary
notification after all items are processed.
Version bump: 1.67.0 → 1.68.0 (SDK + Desktop)
Add biometric unlock (Touch ID / Windows Hello) support
Details
Implement platform-native biometric authentication as a convenience option
for unlocking PrivStack. The master password remains the root of trust —
biometric enrollment stores it in the OS keychain (macOS Keychain / Windows
Credential Manager), gated by biometric access control. On unlock, biometric
verification retrieves the password and feeds it through the existing
UnlockApp path. Zero Rust core changes required.
New files:
- IBiometricService interface with IsSupported, IsAvailable, Enroll,
Authenticate, Unenroll
- MacBiometricService: Security.framework P/Invoke for Keychain +
LocalAuthentication.framework for Touch ID prompts
- WindowsBiometricService: advapi32.dll CredWrite/Read/Delete +
UserConsentVerifier for Windows Hello
- NullBiometricService: Linux/unsupported fallback
Integration points:
- UnlockView: biometric button with "or" separator, auto-attempts on load
- SensitiveUnlockOverlay: biometric option for re-authentication
- Settings > Security: toggle with inline password enrollment, auto
re-enrollment on password change
- Setup Wizard: optional biometric step after password creation (shown only
when hardware is available)
- AppSettings: BiometricUnlockEnabled persisted setting
- ServiceRegistration: platform-conditional DI registration
Version: 1.66.5 → 1.67.0
Desktop v1.66.0: Duncan AI panel — 3 display modes
Details
Add three display modes for the Duncan AI assistant panel so it no
longer has to consume main window real estate:
1. Attached Full Height (default) — right-side drawer, full height,
same as previous behavior.
2. Attached Half Height — right-side drawer anchored to the bottom
half of the window. Drag resize handle is hidden in this mode;
corner radius adapts to top-left only.
3. Detached / Floating — separate OS window that can be moved to
another monitor, freely resized, and closed independently.
Implementation details:
- New AiTrayDisplayMode enum (AttachedFull, AttachedHalf, Detached)
drives all mode logic from MainWindowViewModel.
- Computed booleans (IsAiTrayAttachedFull, IsAiTrayAttachedHalf,
IsAiTrayDetached, IsAiTrayAttached) simplify XAML bindings.
- IsAiTrayDrawerOpen gates the slide animation so the inline drawer
stays hidden when the tray is in detached mode.
- AiTrayMaxHeight property constrains the drawer to half the window
content height, updated on window resize via code-behind.
- Three small icon buttons in the drawer header let the user switch
modes. Active mode gets accent/highlight styling.
- DetachAiTray() removes the AiSuggestionTray UserControl from the
inline grid and reparents it into a new AiTrayWindow (non-modal,
resizable, native OS decorations). ReattachAiTray() reverses the
operation, returning the UserControl to the inline drawer.
- Closing the floating window via the OS close button automatically
reattaches to AttachedFull mode.
- Status bar star icon brings the floating window to front when in
detached mode, toggles the drawer when in attached modes.
- HalfValueConverter added for potential future XAML-side bindings.
- App shutdown sequence cleanly closes the floating window.
Get notified about new releases