Fix client mode startup: license check and error handling
Details
In client mode, ShowMainWindow crashed at LicenseExpirationService because
PrivStackService.GetLicenseStatus() requires the native runtime to be
initialized — which it isn't in client mode. The exception was silently
swallowed by the fire-and-forget task, leaving the app with no visible
window.
- Add license_status field to /api/v1/status endpoint response
- Parse and store server license status during TryEnterClientMode()
- Add CheckLicenseStatusFromServer() to LicenseExpirationService for
applying license status from a remote server string
- Use server-provided license status in client mode ShowMainWindow
- Add ContinueWith error handler on fire-and-forget EnterClientModeAsync
to log failures instead of silently swallowing them
Route entity type registration through SDK transport layer
Details
RegisterEntityType was called directly via NativeLib P/Invoke in both
PluginRegistry (Desktop) and HeadlessPluginRegistry (Server), bypassing
the ISdkTransport abstraction. In client mode, the native runtime isn't
initialized, causing all entity schema registrations to fail with error
code -4.
- Add RegisterEntityType to ISdkTransport interface
- Implement in FfiSdkTransport (delegates to NativeLib)
- Implement in HttpSdkTransport (POSTs to /api/v1/sdk/register-entity-type)
- Add public RegisterEntityType method on SdkHost
- Add /sdk/register-entity-type endpoint to LocalApiServer
- Update PluginRegistry to route through SdkHost instead of NativeLib
- Update HeadlessPluginRegistry with same fix, remove unused NativeLib import
Add SDK transport abstraction for Desktop client mode
Details
Extract FFI calls from SdkHost into ISdkTransport interface with two
implementations:
- FfiSdkTransport: wraps NativeLibrary P/Invoke calls with pointer
marshalling (standalone mode, existing behavior)
- HttpSdkTransport: proxies SDK calls over HTTP to a running headless
server (client mode, new)
When Desktop detects a running headless server at startup (probes
GET /api/v1/status), it switches to client mode: swaps the transport
from FFI to HTTP, skips DuckDB initialization and the unlock screen,
and routes all plugin data operations through the server. This solves
the DuckDB single-process limitation during development — server for
API testing, Desktop for UI verification, both against the same data.
Server-side: LocalApiServer gains SDK passthrough endpoints under
/api/v1/sdk/* (execute, search, vault, blob, db maintenance) so the
HttpSdkTransport has something to call. All endpoints sit behind the
existing API key authentication.
In client mode, server-managed background services (backup, file sync,
snapshot sync, reminders, RAG indexing, API server) are skipped since
the headless server handles them. UI services, plugin discovery, and
IPC still run locally.
SdkHost.SetTransport() follows the same post-build wiring pattern as
SetSyncOutbound() and SetVaultUnlockPrompt(). Pure refactor in
standalone mode — zero behavior change when no server is detected.
Fix AXAML xmlns references after Services extraction
Details
8 AXAML files still referenced `using:PrivStack.Desktop.Models` which
is now an empty namespace — all model types moved to
`PrivStack.Services.Models` during the architecture split.
Updated xmlns declarations:
- models → PrivStack.Services.Models (8 files)
- BacklinkEntry → PrivStack.Services via new svc: prefix (InfoPanel)
Register entity schemas with Rust core in headless plugin registry
Details
HeadlessPluginRegistry was loading and initializing plugins but never
registering their entity schemas with the Rust core via FFI. This caused
the API to return "No schema registered for entity type: task" errors
because the Rust engine didn't know about the entity types.
Mirrors the desktop PluginRegistry behavior:
- RegisterEntitySchemas() called per-plugin BEFORE InitializeAsync()
- RegisterSystemEntitySchemas() for system types (entity_metadata,
property_definition, property_group, property_template)
- Uses NativeLib alias for PrivStack.Services.Native.NativeLibrary
(InternalsVisibleTo already grants access to privstack-server)
Get notified about new releases