Skip to content

v2.2.0: Barbatos.Wpf.Aquarius, Apsu State Management, and MCP AI Chat

Choose a tag to compare

@StHung StHung released this 25 Jul 06:10

v2.2.0: Barbatos.Wpf.Aquarius, Apsu State Management, and MCP AI Chat

🚀 The big one: Barbatos.Wpf.Aquarius ships as a brand-new package, bringing Vue's
Composition API and template-directive model to plain WPF - reactive Ref/Computed/Watch,
lifecycle hooks, v-model/v-show/v-if/v-on-style directives, Teleport, Transition,
Provide/Inject, Suspense, and named Slots. Alongside it, Barbatos.Wpf.Core gains its
own reactive store framework (Barbatos.Wpf.Apsu), a bring-your-own-key AI chat + MCP client
(Barbatos.Wpf.Mcp), realtime push notifications, a from-scratch Shell_NotifyIcon tray icon,
and installable dotnet new templates for both packages - plus a calendar-anchored redesign of
periodic services and a couple of stability fixes.

⚠️ Breaking Changes

  • Periodic services now schedule on a calendar instead of a bare interval.
    IWpfPeriodicService.Interval (TimeSpan) is replaced by Schedule (PeriodicSchedule);
    IPeriodicServiceScheduler.UpdateInterval(name, TimeSpan) is replaced by
    UpdateSchedule(name, PeriodicSchedule); PeriodicServiceStatus.Interval is replaced by
    .Schedule, joined by new NextRunTime/IsCompleted properties. PeriodicSchedule carries
    a Frequency (Once/Hourly/Daily/Weekly/Monthly/Custom) plus TimeOfDay,
    DaysOfWeek, DayOfMonth, and StartTime/EndTime - a Daily/Weekly/Monthly schedule
    now fires at a specific wall-clock time the way a calendar reminder or Task Scheduler trigger
    would, instead of "every N minutes from whenever the app happened to start." The
    configuration key also moved: Barbatos:PeriodicServices:Intervals:<Name> is now
    Barbatos:PeriodicServices:Schedules:<Name>. Update any IWpfPeriodicService implementation
    and UpdateInterval call site accordingly.

✨ What's New

🌊 Barbatos.Wpf.Aquarius (new package)

Reactive state and composition-style XAML directives for plain WPF, modeled on Vue's
Composition API - no dependency on Core, install standalone or alongside it:

  • Reactivity - Ref<T>/Computed<T>/Watch/NextTick, built directly on
    CommunityToolkit.Mvvm's ObservableObject rather than a competing system.
  • Lifecycle hooks - 11 hooks spanning a ViewModel's create/mount/update/unmount/activate/
    deactivate/error-capture lifecycle (IOnBeforeCreate, IOnCreated, IOnBeforeMount,
    IOnMounted, IOnBeforeUpdate, IOnUpdated, IOnBeforeUnmount, IOnUnmounted,
    IOnActivated, IOnDeactivated, IOnErrorCaptured), 8 of them with an *Async twin.
  • Directives - Directives.Model (v-model), Directives.Show (v-show), If/Else
    (v-if/v-else/v-else-if, the latter expressed as nesting), Directives.Event (v-on),
    Directives.Class/Directives.Style, and support for writing custom directives.
  • Expr - a MarkupExtension that parses and reactively evaluates small XAML-embedded
    expressions (comparison, arithmetic, logical short-circuit, ternary, enum-vs-string equality,
    #ElementName identifiers) via MultiBinding, so If.Condition/Directives.Show can take
    something like a + b >= c or status == "Active" directly instead of only a plain bound
    bool.
  • Teleport/TeleportHost - move content to a different part of the tree (or a different
    Window entirely) without losing its identity, state, or bindings; the README's new
    "Dockable Panels" recipe builds a dock/float panel out of nothing but this.
  • Slot/SlotHost/SlotContent/SlotProvided - free-form named content placeholders for
    a custom control's ControlTemplate, chosen at the use site rather than pre-declared as one
    DependencyProperty per name.
  • Transition/TransitionGroup, Provide/Inject, and Suspense (an explicit
    IsPending + Fallback loading-state control) round out the set.
  • An installable dotnet new aq-view item template scaffolds a matching View/ViewModel pair.

🍱 State management - Barbatos.Wpf.Apsu (new, ships in Barbatos.Wpf.Core)

A small Pinia-like reactive store framework:

  • StoreBase stores registered via AddStore<TStore>(), with [Action]-attributed
    methods and Getter<T> computed values.
  • Action interceptors - IStoreActionInterceptor, composable as global (every store) or
    local (one store type) chains, now with an out InterceptorHandle overload and
    StoreInterceptorBuilder<TStore> fluent registration
    (AddStore<T>(i => i.Add<X>().Add(...))) for declaring a store and its local interceptors in
    one call.
  • Ejectable interceptors - InterceptorHandle.Eject() removes a registered interceptor
    from every chain it applies to at runtime, the axios.interceptors.request.eject(id)
    counterpart.
  • IStorePlugin and IStoreRegistry for cross-cutting store setup and runtime
    discovery.
  • Implemented with Castle.Core-based dynamic proxying under the hood (a new dependency pulled
    in by Barbatos.Wpf.Core).

🤖 AI chat + MCP - Barbatos.Wpf.Mcp (new, ships in Barbatos.Wpf.Core)

A Model Context Protocol client and bring-your-own-key AI chat service - your app's own end
user supplies their own Anthropic/OpenAI/other API key, so you never pay for their usage:

  • IMcpServerRegistry/McpServerDescriptor to register and track MCP servers (stdio or
    HTTP transport) and their connection status.
  • IAiChatService/IAiChatClientFactory for the actual chat calls, with built-in
    Anthropic and OpenAI provider factories. Provider is a free-form string rather than an
    enum, and AiProviderOptions.Providers is an optional catalog you can switch between at
    runtime via SelectProvider(key).
  • IAiApiKeyProvider, backed by SecureStorage by default, so end users' own keys are
    never stored in plain text.

🔔 Realtime push notifications (new, ships in Barbatos.Wpf.Core)

IPushNotificationService listens for incoming notifications from your own push server and
displays each one through the existing INotificationService toast pipeline, falling back to
a small in-app window whenever a real toast isn't available:

  • IPushNotificationTransport keeps the delivery mechanism swappable; the bundled
    SignalRPushNotificationTransport is the default, but every SignalR-specific detail (hub
    URL, method names) stays out of the transport-agnostic surface.
  • Deserialize into your own type via IPushNotification/ConfigurePushNotifications<T>(), or
    use the bundled PushNotification (Title/Body/ImageUrl/Action).
  • A notification's Action (Url/Setting/Route/None) auto-dispatches for Url/
    Setting; Route raises RouteRequested for your own app to handle.

🖥️ Tray icon rewritten on raw Win32

ITrayIconPlatform's default implementation no longer goes through
System.Windows.Forms.NotifyIcon - it now talks to Shell_NotifyIcon directly through a
hidden message-only window, so the tray icon feature no longer pulls in a
System.Windows.Forms reference. Also new: ITrayIconService.ShowBalloonTip(title, text, icon) with a TrayIconBalloonIcon (None/Info/Warning/Error), which checks
SHQueryUserNotificationState first so balloons don't get silently discarded during a locked/
full-screen session.

📦 dotnet new templates

Installable project and item templates for both packages, listed in templates/README.md:

dotnet new install ./templates/Barbatos.Wpf.Aquarius/item-templates/aquarius-view
dotnet new install ./templates/Combined/project-templates/barbatos-wpf-app

dotnet new barbatos-wpf-app -n MyApp      # Core + Aquarius + Barbatos.i18n starter
dotnet new aq-view -n Dashboard --namespace MyApp.Features.Dashboard

Both also show up in Visual Studio 2022's and Rider's own New Project/New Item dialogs once
installed, in addition to the CLI.

🛠️ Fixes

  • Disposing the app host on exit could reenter WpfApplication's own lifecycle overrides
    mid-dispose (e.g. via the tray icon's native window teardown) and throw
    ObjectDisposedException instead of exiting cleanly. The host reference is now cleared
    before it's disposed, so any reentrant call becomes a no-op.
  • Closing an owned dialog didn't always hand activation back to its owner - most noticeable
    with an IDE debugger attached, which could steal the foreground instead. The owner is now
    explicitly reactivated when its dialog closes.

📦 Updating via NuGet

dotnet add package Barbatos.Wpf.Core --version 2.2.0
dotnet add package Barbatos.Wpf.Aquarius --version 2.2.0

If you implement IWpfPeriodicService or call IPeriodicServiceScheduler.UpdateInterval
directly, see Breaking Changes above before upgrading.