Add IApiProvider SDK capability and local HTTP API server
Details
New SDK capability interface (IApiProvider) lets plugins declare HTTP API
routes and handle requests via pure SDK DTOs — no Kestrel dependency in
plugins. The shell hosts a Kestrel minimal API server on 127.0.0.1:9720
(configurable) that discovers providers and maps routes.
- IApiProvider interface + ApiProviderModels DTOs (ApiMethod, ApiRouteDescriptor,
ApiRequest, ApiResponse with static factory methods)
- LocalApiServer: WebApplication.CreateSlimBuilder(), API key auth middleware
(constant-time compare), per-provider route mapping, /api/v1/status (no auth)
and /api/v1/routes shell endpoints
- AppSettings: ApiEnabled (default false), ApiPort (default 9720), ApiKey
(auto-generated base64url on first enable)
- ServiceRegistration: LocalApiServer singleton
- App.axaml.cs: conditional server start in deferred background services
- PluginRegistry.ActivatePlugin: auto-register IApiProvider to CapabilityBroker
- FrameworkReference Microsoft.AspNetCore.App in Desktop csproj
Add stop_reason/finish_reason validation to all AI providers
Details
All cloud AI providers (Anthropic, OpenAI, Gemini, Groq, Mistral) now
check the API's stop/finish reason field to detect token-limit truncation.
Previously, truncated responses were returned as Success=true with
partial content, causing garbled output or literal "MAX_TOKENS" error
strings to leak through to the user.
Changes:
- AiResponse: add WasTruncated property for consumer-side handling
- AnthropicProvider: check stop_reason == "max_tokens"
- OpenAiProvider: check finish_reason == "length"
- OpenAiCompatibleProviderBase: check finish_reason == "length" (covers
Groq, Mistral, and any future OpenAI-compatible providers)
- GeminiProvider: check finishReason == "MAX_TOKENS", log warning
- All providers log a warning with the MaxTokens limit when truncated
Add BoxShadows elevation token system to SharedTokens
Details
Define a 5-tier elevation shadow system as BoxShadows resources:
ThemeShadowXs — cards at rest, subtle depth cue
ThemeShadowSm — surface cards, raised sections
ThemeShadowMd — elevated cards, hover lift, floating toolbars
ThemeShadowLg — menus, popovers, dropdowns, toasts
ThemeShadowXl — modals, dialogs, full-screen overlays
Plus two hover-state tokens (ThemeShadowXsHover, ThemeShadowSmHover)
for cards that lift on pointer-over.
Migrate all theme style classes (card, surface-card, elevated-card,
stat-card, item-card, modal, shadow-sm/md/lg, hoverable) and the
menu/context-menu templates to reference these tokens instead of
hardcoded values. Also migrate inline shadows in MainWindow,
UniversalSearchDropdown, and ToastContainer.
Previously there were 80+ hardcoded shadow values with inconsistent
opacity levels across the codebase. This establishes a single source
of truth that can be tuned in one place.
Fix context menu drop shadow clipped by popup window bounds
Details
The BoxShadow on MenuFlyoutPresenter and ContextMenu was invisible
because the Popup native window sizes exactly to its child, clipping
any shadow that extends outside the Border bounds.
Override the control templates to wrap the LayoutRoot Border in a Panel
with margin (12,6,12,22) matching the shadow extent (blur=24 offset-y=8).
This inflates the popup window, giving the shadow room to render. The
BoxShadow is now set directly on the template Border instead of via a
separate style selector.
Add fade-in animation to context menus and menu flyouts
Details
MenuFlyoutPresenter and ContextMenu now have a subtle 120ms open
animation: fade from opacity 0 to 1 with a -4px Y translate slide-in,
using CubicEaseOut easing. The animation runs each time the popup
enters the visual tree (i.e., each open).
Drop shadows were already defined (0 8 24 0 #40000000, 0 2 6 0
#28000000) — this adds the missing entrance transition.
Get notified about new releases