Skip to content

Releases: Runic-Artifex/runic-sdk

Runic SDK 0.7.0-preview.6

Pre-release

Choose a tag to compare

@github-actions github-actions released this 09 Oct 21:56
3ae2693

Runic SDK 0.7.0-preview.6

This preview improves the external application path exercised by ForgeConnect.
Install matching 0.7.0-preview.6 packages from NuGet and npm. Command Line
versions remain independent of this SDK.

Desktop and platform services

  • Awaited WebUI commands and operation waits now admit concurrent control
    callbacks, including Cancel and draft edits. Accepted callbacks remain owned
    through disconnect and shutdown.
  • IFileDialogs.OpenDirectoryAsync returns an owned IDirectoryLease with an
    exact local path for C# application work. Dispose its access after use or
    retain it in the presentation scope while the application needs the grant.
    The platform.directories.open capability reports availability.
  • Native directory selection uses Windows Common Item Dialogs, macOS panels,
    GTK3's compatibility chooser or the Linux desktop portal. Portal directory
    selection requires FileChooser version 3. Existing custom picker backends and
    file-dialog implementations remain compatible and report directory selection
    unavailable until they implement it.

Operations and shutdown

  • createOperationController and Svelte's useOperation expose Start admission,
    pending state, terminal outcomes and cancellation feedback. Observation
    disposal detaches the UI; it does not cancel accepted application mutations.
  • createLatestOperationController coalesces read selections, retains Cancel
    through delayed Start receipts and waits for terminal completion before the
    next admission. Applications can supply an additional completion barrier when
    their accepted work outlives the invocation wrapper. Unknown completion blocks
    further admission until the application explicitly replaces the session.
  • AcceptedWorkScope reserves ownership before invoking a task factory and
    drains actual accepted tasks, including application recovery. Cancelling an
    invocation or a drain observer does not release that ownership. Register the
    complete task and await scope disposal before releasing its dependencies.
    WindowContentSession completion still describes bridge invocations; it does
    not independently prove that all application work has finished.

Shared DTO contracts and creator guidance

  • Add [assembly: RunicBridgeJsonIgnore] to the assembly declaring a ViewModel
    to exclude unconditional JsonIgnore DTO properties throughout its bridge
    graph. Independent Core DTO assemblies need no Runic dependency. This is
    opt-in: existing contracts and root ViewModel property rules are unchanged.
    Never, WhenWritingNull and WhenWritingDefault remain bridge members.
  • Constructor diagnostics explain the difference between serializer exclusion
    and bridge exclusion and point to the opt-in policy or RunicIgnore.
  • Creator documentation uses --directory for the destination. Its global
    --output option selects the CLI output format.

These changes retain exact application-owned paths in C# and existing command
availability rules. Cancellation requests do not imply rollback or completion
of durable work; applications continue to own recovery and mutation ordering.

What's Changed

Full Changelog: v0.7.0-preview.5...v0.7.0-preview.6

Runic SDK 0.7.0-preview.5

Pre-release

Choose a tag to compare

@github-actions github-actions released this 09 Oct 08:37
0ef2a51

Runic SDK 0.7.0-preview.5

This preview extends gradual WPF migration: adopt navigation in a native app,
then replace individual Views with web Views while keeping the same models,
navigation entries and application services.

WPF-hosted web Views (experimental)

  • The new Runic.Application.Wpf package embeds a child WebView2 presentation
    in an existing WPF shell on .NET 10. RunicWebView and CreateWpfViewAsync
    reuse Desktop's authenticated surface and transport. The binding borrows
    the model, services and model context supplied by the application.
  • Mounting waits for WPF loading. Unload cancels pending work, closes the child
    session and drains accepted operations before releasing its owned resources.
  • The hybrid editor example
    switches between native controls and a generated web client for the same
    model and navigation entry. Validation, dirty state, save errors, cancellation
    and departure confirmation remain in the shared model. If web mounting fails,
    the native editor remains usable and preserves the draft.

Install Runic.Application.Wpf at 0.7.0-preview.5 for this optional
presentation. See its README
for setup and ownership rules. Native-only navigation continues to use
Runic.Navigation and Runic.Navigation.Wpf.

Navigation and MVVM integration

  • Overlapping Back requests now share one transition and one confirmation,
    including token-bearing requests from ReactiveUI commands. Each caller can
    cancel independently; the shared Back cancels when all callers cancel before
    commit. This fixes confirmation dialogs closing and reopening on repeated Back.
  • RunicModelContext now exposes its Closed token through
    IRunicModelContextLifetime, matching the WPF dispatcher context. Its
    navigator starts closing immediately and rejects later requests as Closed,
    even while an earlier hook ignores cancellation.
  • Both ReactiveUI flavors add WhenCanGoBackChanged() and
    WhenIsTransitioningChanged() for native observable/command composition.
    Toolkit consumers compose their async commands with the shared engine; the
    WPF example forwards cancellation and dismisses an already-open result prompt.
  • NavigationSelector.Region connects single selection to borrowed Replace
    requests, including typed regions. It restores selection after rejection and
    cancels pending selection on unload. A TabControl receives a default
    NavigationHost content template when the application supplied none.
  • ViewHost locates a View for plain borrowed content without navigation entries
    or entry scopes. INavigationViewLocator.ResolveView(object, IServiceProvider)
    is a new default interface overload; existing entry-only locators return null
    for it, while the registered locator supports both forms.

Compatibility: the experimental INavigationRegion interface adds the
required ReplaceBorrowedAsync member for presentation adapters. Runic's region
implements it; custom interface implementations must implement the new member
and recompile. Selection retains application ownership of the supplied content.

Generator

  • CommunityToolkit's generated IncludeCancelCommand commands are supported by
    the bridge generator and execute the actual Toolkit cancel command. Arbitrary
    ICommand members still need a supported execution contract.
  • Generated bridge and TypeScript sources use LF consistently across platforms,
    avoiding changes to checked-in generated files when building on Windows.

Known limits

Navigation integrations remain experimental (RUNICNAV001), and the new WPF
WebView adapter is experimental (RUNICWPF001). Embedded WebView2 follows normal
HWND airspace constraints. Tab enters the web document; automatic traversal from
its boundary back to surrounding WPF controls remains a prototype limitation.

What's Changed

Full Changelog: v0.7.0-preview.4...v0.7.0-preview.5

Runic SDK 0.7.0-preview.4

Pre-release

Choose a tag to compare

@github-actions github-actions released this 08 Oct 18:50
1d10622

Runic SDK 0.7.0-preview.4

This preview makes Runic navigation usable in an existing WPF app on .NET 10
without the Views runtime, the web View or the build generator:

  • Runic.Navigation is now its own package, and Runic.Navigation.Wpf adds a
    NavigationHost, a dialog host and a dispatcher-backed model context.
  • The ReactiveUI helpers moved to Runic.Navigation.ReactiveUI and
    Runic.Navigation.ReactiveUI.Reactive.
  • Navigation gains container-built targets (PushAsync<T>()), per-entry
    service scopes, LeaveConfirmation and DismissAsync.
  • The WPF navigation example
    shows four common scenarios. Its
    comparison
    builds the same app with Prism, ReactiveUI and CrissCross.

Breaking: navigation and model context types moved to the
Runic.Navigation namespace and package, and the ReactiveUI helpers moved to
their own packages. There are no type forwards. Add the using directives
and recompile; the tables below list every moved type.

Views

  • A project that references Runic.Application (or builds in-repo) but declares
    no Runic Window or View, for example one that only uses the navigator, now
    builds without <RunicBridgeBuildEnabled>false</RunicBridgeBuildEnabled>. When
    bridge generation is on only by default and the assembly has no Window or View,
    the build generates nothing and skips the frontend install, build and copy
    steps. RUNICBRIDGE006 remains an error when the project sets
    RunicBridgeBuildEnabled to true, has a frontend package.json, declares
    RunicBridgeFrontendInput items, or sets RunicBridgeCompositionType or
    RunicBridgeModelAssembly. Projects that reference a host adapter
    (Runic.Application.CsWebUi or Runic.Application.Desktop) still always require
    generation.

Runic.Navigation package

Navigation and the model context moved out of Runic.Application into the new
Runic.Navigation package and namespace. Runic.Navigation has no build
targets, generator or host, and depends only on
Microsoft.Extensions.DependencyInjection.Abstractions and
Microsoft.Extensions.Logging.Abstractions, so a WPF or console project can
reference it on its own. Runic.Application depends on it, so Views apps get
it transitively.

This is a source and binary break. There are no type forwards: add
using Runic.Navigation; where you use these types and recompile.

Type Before Now
IRunicModelContext, IRunicModelContextLease, RunicModelContext, RunicModelContextRegistry Runic.Application.Views in Runic.Application Runic.Navigation in Runic.Navigation
RunicNavigator, RunicNavigatorOptions, RunicNavigationServiceCollectionExtensions (AddRunicNavigation) Runic.Application.Views in Runic.Application Runic.Navigation in Runic.Navigation
NavigationRegion<TContent>, NavigationRegionOptions, NavigationChildRetention, NavigationEntry<TContent>, NavigationEntryId, NavigationEntryState, NavigationEntryContext Runic.Application.Views in Runic.Application Runic.Navigation in Runic.Navigation
NavigationTarget, INavigationTarget<TContent>, NavigationOwnership, NavigationRequestOptions, NavigationOperation Runic.Application.Views in Runic.Application Runic.Navigation in Runic.Navigation
NavigationResult<TContent>, NavigationRejection, NavigationPhase, NavigationResultRequest<TContent, TResult>, NavigationCompletion<TResult> Runic.Application.Views in Runic.Application Runic.Navigation in Runic.Navigation
INavigationDepartureGuard, NavigationDeparture, NavigationDepartureKind, INavigationInitialize, INavigationInitialize<TInput>, INavigationInputInitialize, INavigationResume, NavigationResume Runic.Application.Views in Runic.Application Runic.Navigation in Runic.Navigation
INavigationRegion, INavigationEntry, INavigationPresentation (new in this release, see below) — Runic.Navigation in Runic.Navigation
  • The model context types stay supported API; the navigation types stay
    experimental (RUNICNAV001).
  • These members of other packages now take or return the moved types, so
    callers recompile: WindowContentSession's constructors and ModelContext,
    WindowContentSessionOptions.ModelContext, the RunicWindowTestHost<TViewModel>
    constructor and RunicWindowTestHostOptions.ModelContext
    (Runic.Application.Testing). The ReactiveUI helpers moved to their own
    packages, listed below.
  • Log categories: RunicModelContext logs 1030-1033 through
    ILogger<RunicModelContext>, so its category changes from
    Runic.Application.Views.RunicModelContext to
    Runic.Navigation.RunicModelContext. The navigator's category change is
    listed below. Event IDs and messages are unchanged. A window session's own
    context shutdown failure (1033) stays under Runic.Application.Views.
  • Generated bridges don't name navigation types, so generated code needs no
    new using. The packed generator carries Runic.Navigation.dll.

ReactiveUI navigation packages

The ReactiveUI helpers for the model context and navigation regions moved out of
Runic.Application.ReactiveUI and Runic.Application.ReactiveUI.Reactive into
two new packages, Runic.Navigation.ReactiveUI and
Runic.Navigation.ReactiveUI.Reactive. Each depends on Runic.Navigation and
the ReactiveUI package of its flavor only, with no Runic.Application, so a
WPF or console app can use them on their own. Each Application adapter
references the navigation adapter of its own flavor, so Views apps still get
them transitively.

This is a source and binary break. There are no type forwards: change the
using and recompile. The signatures are unchanged except for the namespaces
of the types they mention (see the Runic.Navigation table above for IRunicModelContext,
NavigationRegion<TContent> and NavigationResult<TContent>).

Type Before (namespace and package) Now (namespace in package)
IRunicReactiveSchedulerProvider, RunicReactiveSchedulerProvider Runic.Application.Views.ReactiveUI (package Runic.Application.ReactiveUI) Runic.Navigation.ReactiveUI (package Runic.Navigation.ReactiveUI)
ReactiveNavigation (WhenCurrentChanged, WhenEntryChanged, CreateBackCommand) Runic.Application.Views.ReactiveUI (package Runic.Application.ReactiveUI) Runic.Navigation.ReactiveUI (package Runic.Navigation.ReactiveUI)
ReactiveServiceCollectionExtensions (AddRunicReactiveModelContext) Runic.Application.Views.ReactiveUI (package Runic.Application.ReactiveUI) Runic.Navigation.ReactiveUI (package Runic.Navigation.ReactiveUI)
The same types, System.Reactive flavor Runic.Application.Views.ReactiveUI.Reactive (package Runic.Application.ReactiveUI.Reactive) Runic.Navigation.ReactiveUI.Reactive (package Runic.Navigation.ReactiveUI.Reactive)
  • ReactiveRunicView, ReactiveRunicWindow, ReactiveRoutedRegion<T>,
    ReactiveCommandExecution, ReactiveInteractionDescriptor,
    BridgeSnapshotObservableExtensions and RunicReactiveExceptions stay in the
    Application adapters and their namespaces.
  • ModelScheduler registration. AddRunicReactiveModelContext registers
    ISequencer (IScheduler in the System.Reactive flavor) as transient instead
    of scoped. RunicReactiveSchedulerProvider.For(context) now returns one
    scheduler per context for as long as the context is alive, and the transient
    registration returns that instance, so every resolution for one context still
    gets the same scheduler. That also holds for a singleton context, and a
    singleton ViewModel can inject it under ValidateScopes. A custom
    IRunicReactiveSchedulerProvider is called once per resolution instead of once
    per scope. With a scoped model context, a singleton ViewModel that injects
    ISequencer/IScheduler now fails at resolve time instead of at
    ValidateOnBuild, because the transient factory hides the scoped dependency.
  • Flavor guard. Runic.Application.ReactiveUI.Reactive also rejects a direct
    reference to Runic.Navigation.ReactiveUI.
  • The adapters' log category is Runic.Navigation.ReactiveUI; events 1043-1049
    stay reserved for them.

Runic.Navigation.Wpf package

The new Runic.Navigation.Wpf package (net10.0-windows, experimental,
RUNICNAV001) presents navigation regions in WPF. It depends on
Runic.Navigation and WPF only. See its
README.

  • DispatcherModelContext runs model turns on a WPF Dispatcher and
    implements IRunicModelHookScheduler, so guards, initialize and resume
    hooks, factories and owned disposal run on the UI thread. It closes when the
    dispatcher starts shutting down. A hook that is still awaiting then continues
    on the thread pool, so disposal doesn't hang. Turns dropped by the close are
    reported on the thread that closes the context.
  • NavigationHost presents a region's current entry with a new presenter and
    View per entry. Views come from the host's INavigationViewLocator, the
    registered locator (MapView, UseViewNamingConvention) or the implicit
    DataTemplate. It handles NavigationCommands.BrowseBack. A locator View's
    constructor parameter typed as the content's class or a base class, never
    object or an interface, gets the entry's content.
    UseViewNamingConvention(params Assembly[]) also searches View assemblies
    apart from the ViewModels'.
  • NavigationDialogHost shows each entry of a region in an owned window with
    Show(), never ShowDialog(). It disables the thread's other windows
    (Modality="Application") or the owner chain (Owner), turns a u...
Read more

Runic SDK 0.7.0-preview.3

Pre-release

Choose a tag to compare

@github-actions github-actions released this 08 Oct 07:05
e59a8da

Runic SDK 0.7.0-preview.3

Views

  • WindowContentSession.Forget is now a no-op after the session is disposed, like
    ClearOwner; it threw ObjectDisposedException before. A navigator retiring
    owned content during window close relies on this.
    It also now waits for a detachment of that content already in progress on
    another thread, so its routes are gone when it returns; calling it inside a
    model turn while the same content detaches off-turn can deadlock.

Desktop

  • DesktopNativeOwner in Runic.Application.Desktop is a shipped
    INativePickerOwner for an embedded Desktop window, and
    DesktopBridgeWindow<TViewModel>.NativeOwner exposes it for each opened
    Window. Pass it to the platform providers for file dialogs, file launchers and
    the clipboard instead of writing an owner adapter. Runic.Application.Desktop
    now depends on Runic.Platform.Runtime.
  • DesktopHostOptions.WithGtk4() in Runic.Desktop.Gtk4 applies the GTK 4
    profile: it selects LinuxEmbeddedBackend.Gtk4WebKit6 and sets
    WindowHostFactory to Gtk4WindowHostFactory together. On Windows and macOS
    it keeps the platform host. It throws ArgumentException when another factory
    is already set.
  • Availability reports every missing embedded prerequisite, not only the first.
    DesktopPresentationAvailability.Diagnostics and
    DesktopPresentationPreflight.Diagnostics list them; Diagnostic stays the
    first entry, and setting Diagnostic (for example with with) replaces the
    list with that one diagnostic. Availabilities compare their diagnostics by
    value, so two identical GetAvailability() results are equal.
    DesktopHost.Validate and DesktopAvailabilityResult.Diagnostics
    now include all of them. A GTK4 selection without its provider lists
    gtk4-provider-missing, the new gtk4-runtime-missing,
    webkitgtk6-runtime-missing and graphical-session-missing together; a GTK3
    selection lists webkitgtk-runtime-missing and graphical-session-missing
    together. Code that counted Validate errors may now see more than one.
  • IDesktopWindowHostFactory.GetAvailabilityDiagnostics() (default: none) lets
    an unavailable factory replace the generic custom-window-host-unavailable
    diagnostic with its own list; a code the host already reports, such as a
    toolkit conflict, is listed once. Gtk4WindowHostFactory reports each missing
    library and a toolkit conflict.
  • LinuxDesktopRuntime.IsLibraryAvailable caches each definite result for the
    process lifetime and keeps the ldconfig cache once it has read it, so
    availability checks no longer start an ldconfig process per missing library.
    A failed or timed-out ldconfig read is not cached; the next check retries
    it. A library installed while the process runs is seen by the next process.
  • DesktopEventLoop.Run(options, application) is one entry point for every
    platform and backend. It starts a DesktopHost, runs the application on the
    loop its window host needs, disposes the host and returns the application's
    exit code. A supported IDesktopEventLoopWindowHostFactory, such as the GTK 4
    factory from WithGtk4(), runs its own loop (Gtk4Application.Run). macOS
    uses the AppKit loop. Windows and Linux GTK 3 wait for the work. The options
    are validated and the host is built before any loop starts. An event-loop
    factory whose prerequisites are missing, or whose
    IDesktopEventLoopWindowHostFactory.PrepareEventLoop() fails, is not started.
    The reasons are reported to DiagnosticSink and the logger, and a browser
    fallback still works. For example, GTK 4 opens its display with
    gtk_init_check and reports gtk4-display-unavailable instead of exiting the
    process. A nested DesktopEventLoop.Run throws on every platform. Gtk4WindowHostFactory.ApplicationId and
    WithGtk4(applicationId) set the GTK application ID.
  • The runic-app template has a desktop-gtk4 host (--host desktop-gtk4,
    also offered by Runic.Create). It selects GTK 4 and WebKitGTK 6 on Linux
    with WithGtk4() and references Runic.Desktop.Gtk4,
    Runic.Platform.Linux.Gtk4 and Runic.Platform.Linux.Portal. Both Desktop
    hosts now start with DesktopEventLoop.Run(options, ...) and write Desktop
    diagnostics, such as an embedded-to-browser fallback, to standard error. On
    Windows and macOS desktop-gtk4 behaves like desktop.
  • RUNIC_APPLICATION_CLOSE_AFTER_OPEN=1 makes OpenDesktopWindowAsync close
    every window it opens, once the window has opened and connected. Before
    closing, it warns on standard error (and logs event 2002) and prints
    RUNIC_APPLICATION_OPENED=<presentation>. This lets a template-shaped Desktop
    application run under automation. Template acceptance uses it to run the desktop-gtk4 variant under
    Xvfb (RUNIC_TEMPLATE_DESKTOP_SMOKE=1).
  • dotnet runic doctor adds a gtk4-profile check for projects that reference
    Runic.Desktop.Gtk4. One warning lists every missing piece:
    Runic.Platform.Linux.Gtk4 (portal parent and clipboard),
    Runic.Platform.Linux.Portal, and, on Linux without --rid, the GTK 4 and
    WebKitGTK 6 libraries and a GTK older than 4.12. It names the packages to
    install for Debian/Ubuntu, Fedora, Arch, openSUSE and NixOS (from
    /etc/os-release, or /usr/lib/os-release when that is missing; on NixOS
    the libraries must also be on the loader path), and the library file names
    elsewhere. It also warns when the project restores Runic.Platform.Linux,
    whose GTK 3 portal parent loads GTK 3 into the GTK 4 process. Doctor and the
    runtime accept both libwebkitgtk-6.0.so.4 and libwebkitgtk-6.0.so.0.

Experimental

  • Typed navigation (#61): RunicNavigator, NavigationRegion<TContent> and
    NavigationTarget in Runic.Application give each region stable entry ids,
    awaited departure guards (also when a push retains the current entry),
    initialize-once and resume-on-return hooks, supersession of overlapping
    requests, parent-owned child regions, and owned or borrowed content.
    AddRunicNavigation() registers one model context and one navigator per
    window scope. Every type is marked [Experimental("RUNICNAV001")]; suppress
    RUNICNAV001 to use it and expect changes before it is supported. Navigation
    logs events 1060-1071 under Runic.Application.Views; 1072-1079 are reserved
    for later navigation events and 1043-1049 for the ReactiveUI navigation adapter.
  • NavigationRegion<TContent>.PushForResult<TResult> pushes an entry and returns
    a NavigationResultRequest whose Completion ends Completed(value) when the
    entry calls NavigationEntryContext.CompleteAsync(value) and the Back it issues
    commits, and Dismissed on every other path: the push does not commit, the
    entry retires another way, the caller's token is cancelled after the commit
    (which also goes back from the entry) or the navigator closes. A Back from
    CompleteAsync or caller cancellation may leave the region empty, so a confirm
    can be pushed into an empty dialog region; a plain BackAsync from a single
    entry is still NoHistory. A push onto an entry whose caller cancelled retires
    that entry rather than retaining it. If the Back that cancellation issues is
    rejected, for example because a push over the entry was already committing, the dismissed entry stays in the
    history, and its later CompleteAsync drops the value. CompleteAsync accepts
    any value of the result type at run time, so true completes a bool? request
    and 5 an object request. Events 1070 (dismissed, with a reason) and 1071
    (a late value dropped) log at Debug.
  • A get-only NavigationRegion<TContent> property is a generated content slot
    that presents the region's Current. Its generated TypeScript type, the
    union of the presentable PageReference types, always includes | null,
    so moving a non-null content property to a region adds | null and the
    frontend must handle the empty state. A
    NavigationRegion<object> slot, or one with a public setter, is
    RUNICBRIDGE008. After a commit, departing owned content is forgotten before
    the next state capture presents the new Current; until then an invocation on
    the old reference is rejected like any forgotten route.
  • Disposing a navigator cancels window-owned work its owned content started and
    then disposes that content; deferring disposal until such operations drain is
    planned with operation presentation.
  • Runic.Application.Testing adds UnretiredEntryCount() and
    RetainedContentModelCount() for navigation lifecycle tests.
  • Runic.Application.ReactiveUI and Runic.Application.ReactiveUI.Reactive add
    WhenCurrentChanged(), WhenEntryChanged() and CreateBackCommand(scheduler)
    for navigation regions, also marked [Experimental("RUNICNAV001")]. The back
    command can execute while the region can go back and is not transitioning,
    and its output is the NavigationResult; dispose it with its owner, because
    it observes the region until then. ReactiveRoutedRegion<T> is
    unchanged; the ReactiveUI README explains when to use each. Views events
    1043-1049 stay reserved for the adapter, which logs none yet.

Examples

  • The CommunityToolkit Notes example navigates with RunicNavigator. Main
    and Dialog are regions, and the shell presents their Current as | null
    slots. Each visit to the notes pushes a new owned document with a
    CurrentPane child region. The window-scoped editor keeps the draft. The
    unsaved-edits confirm is a departure guard that awaits PushForResult<bool>.
    The sidebar commands are now asynchronous and complete when the navigation
    ends. The confirm is modal: the navigation commands are unavailable while it
    asks or while a page navigation runs, and a confirmed departure discards the
    draft only when the Back commits.

What's Changed

Read more

Runic SDK 0.7.0-preview.2

Pre-release

Choose a tag to compare

@github-actions github-actions released this 07 Oct 23:15
0e915be

Runic SDK 0.7.0-preview.2

Tools

dotnet-runic, the project creator (Runic.Create) and the asset packer use
Runic Command Line 0.6.0-preview.2 (see its
upgrade notes).
For these tools this means:

  • SIGTERM and SIGQUIT now cancel a running command like Ctrl+C. A second signal
    exits at once with 128 plus the signal number (130 for Ctrl+C, 143 for
    SIGTERM, 131 for SIGQUIT). dotnet runic dev is the exception: it ignores
    repeated signals until it has stopped the frontend and application processes.
  • Help wraps to the terminal width, and options whose default is empty no
    longer show an empty default.
  • When a fault message looks like it contains a path or exception text, only
    the message is replaced, with "The command failed; details were redacted.".
    The fault keeps its code instead of becoming RCLI5000.

Desktop

  • A browser window whose browser starts but requests nothing within
    ConnectionTimeout is launched again, with a fresh profile unless the window
    sets one. Chromium occasionally stalls like this on its first start with a new
    profile. DesktopHostOptions.BrowserLaunchAttempts bounds the launches
    (default 2, at most 5; 1 restores a single launch). Stopping a stalled browser
    can take up to about 16 seconds, so with the defaults a window that never
    connects fails after at most about 46 seconds instead of 15. Each relaunch
    reports browser-launch-stalled to the diagnostic sink and logs event 3004,
    and a final timeout names its attempt, such as "on launch attempt 2 of 2". A
    page that was requested, a browser that exited and embedded WebViews are not
    relaunched.
  • A Bridge WebSocket that sends no token check is closed, and the page's Bridge
    reconnects: after 10 seconds, or after 2 seconds when a newer WebSocket needs
    the only connection. Before, without multiple clients, such a socket (for
    example from a page that was replaced) could hold the only connection, and
    reconnects were rejected until ConnectionTimeout; this is the likely cause
    of stalled Windows handshakes (#35), not yet confirmed. Each close logs event
    3005. A WebSocket whose token does not match is closed at once and is never
    authenticated.
  • A connection timeout now lists what the server saw of the Bridge handshake,
    with times since launch: requests, each WebSocket opened or rejected, the
    first message, whether the token matched, and why a socket ended.

Windows administration

  • Local service, scheduled task, firewall rule and SMB share writes in
    Runic.Platform.Administration.Windows were accepted on a disposable
    Windows 11 x64 VM. The run covered create, read, update and delete, and a
    non-elevated process being denied the same writes. The service lifecycle was
    accepted with the NativeAOT verifier only. The package is still experimental,
    and the domain scenarios (LDAP/AD, DNS, Group Policy) are not yet accepted.
    See the package's
    verification guide.

  • The repository's Runic.AdminVerify diagnostic tool:

    • follows the Runic command-line conventions: per-command --help,
      --version, --output json and completion;
    • adds local --expect-denied, which checks that a non-elevated process is
      denied each owned write;
    • always deletes its test service during cleanup, even when stopping it
      fails.

    The local and domain options and the exit codes are unchanged.

Supported platforms

  • eng/support.json now records, per Window host (Runic Desktop and CS-WebUI)
    and runtime identifier, whether the target is CI-verified, packaged but not
    CI-verified, or unsupported, with a reason, plus the platform requirements
    (Windows 11 or a supported Windows 10 release, the WebView2 Runtime,
    macOS 15 for both hosts, glibc 2.34 for CS-WebUI, and GTK 4.12 with
    WebKitGTK 6.0 for Runic.Desktop.Gtk4). The README's
    Supported platforms
    table is generated from it.
  • dotnet runic doctor --rid reads this matrix instead of built-in RID lists.
    The statuses are unchanged; a warning for a target without CI coverage now
    says why, and a failure for an unlisted RID names the CI-verified RIDs of the
    project's host.
  • The compatibility set embedded in dotnet-runic
    (runic.compatibility-set.json) moves to schemaVersion 2 and gains a
    support object. Existing properties are unchanged. The tool reads only the
    copy embedded in itself.

What's Changed

Full Changelog: v0.7.0-preview.1...v0.7.0-preview.2

Runic SDK 0.7.0-preview.1

Pre-release

Choose a tag to compare

@github-actions github-actions released this 07 Oct 16:49
7bc41a7

Runic SDK 0.7.0-preview.1

This preview contains breaking changes; see Upgrading.

Assets

  • AssetManifest.TryGetAsset returns false for an invalid path instead of
    throwing. The new AssetPath.TryNormalize validates a path without throwing.
  • The Desktop and ASP.NET Core adapters accept IAssetSnapshotSource, so a
    source that cannot deliver snapshots is a compile error instead of an
    ArgumentException at run time.
  • Both adapters resolve request paths with the new
    AssetManifest.TryResolveRequestPath and AssetRoutingOptions, so one archive
    routes identically on both hosts. By default the root serves the entry point
    and missing paths without a file extension fall back to it.
  • The packer README
    documents running the packer directly and --trusted-generated-output.
  • Fix: with --output json, packer failures report their RAS1001–RAS1004
    code instead of RCLI5000 ("The command failed unexpectedly."). Fault
    messages no longer contain paths or exception text; the source directory,
    entry point and underlying reason are in fault.details. Values that look
    like home, /tmp or /root paths, drive or UNC paths, or exception text are
    redacted there; other absolute paths, such as macOS /var/folders/... or
    /srv/..., are shown. Human output and exit codes are unchanged.

Application hosts

  • Runic.Application.Desktop and Runic.Application.CsWebUi depend on
    Microsoft.Extensions.DependencyInjection.Abstractions instead of the
    Microsoft.Extensions.DependencyInjection container.
  • The CS-WebUI dependency is updated to 2.5.0-beta.4.6. Large (over ~2 KB)
    Bridge replies no longer intermittently arrive with stray bytes after their
    JSON.
  • Fix: concurrent CS-WebUI Bridge calls no longer occasionally lose their reply
    (#53). The page sends each call once .NET has received the previous one, or
    after at most 1.5 s. After a WebSocket reconnect, calls that were in flight
    when the connection dropped no longer delay the calls that follow.
  • Fix: a CS-WebUI Bridge call in flight when the WebSocket connection drops now
    rejects instead of never settling. Commands and other calls fail with
    BridgeError kind disconnected. An operation start whose reply is lost
    checks the operation's status, and reports BridgeOperationUncertainError
    when that fails, because .NET may already have started it. Interaction
    handlers pause while the connection is down (see Views).
  • Runic.Application.CsWebUi adds ValidateWindow<TViewModel>(), which checks
    at startup that the generated Bridge is registered. OpenWindow runs the same
    check before it creates the native window, so a missing AddRunicViews()
    throws a CsWebUiConfigurationException with bridge-not-registered, the
    code Runic Desktop reports, instead of a dependency-injection error. It
    derives from InvalidOperationException and is logged as event 1050.

Views

  • Fix: interaction handlers of a mounted View resume after a WebSocket
    reconnect remounts it. Before, a View that handled interactions stopped
    receiving them after a reconnect.
  • A failed Bridge call names its route and keeps its cause: BridgeError has
    route, cause and, in development, detail with the .NET exception type,
    message and stack. Production replies keep the bounded message. Development
    means the host environment is Development or
    BridgeDiagnostics.IncludeFailureDetail is true.
  • waitForBridge({ timeout }) reports a missing window.__runicBridge, for
    example a frontend opened from a plain Vite server, as unavailable with
    remediation, and a Bridge that does not connect as timeout.
  • operation.wait({ timeout }) cancels the operation when the timeout passes
    and resolves to a timedOut status.
  • onBridgeDiagnostic(listener) observes runtime failures. While serving,
    @runic-artifex/vite-plugin-runic forwards them to its DevTools dock, which
    shows the latest failure with its stack.
  • Keyed collections: [RunicCollection(nameof(Row.Id))] on a read-only
    collection of DTO rows sends changes as indexed add, remove, replace
    and move frames instead of a full snapshot per change, so unchanged rows
    keep their identity in the browser. Frames apply transactionally and recover
    from gaps, resets and oversized batches with a snapshot. In both ReactiveUI
    adapters, BatchBridgeSnapshots(model) on an observable groups the changes
    of one notification into one frame, which suits DynamicData Bind. The SDK
    has no DynamicData dependency; the
    DynamicData example
    shows a shared 100,000-row cache with per-window viewports. The
    collection delta specification
    has portable fixtures.
  • State snapshots are serialized when they are delivered instead of on every
    change, so a burst of changes behind a busy host costs one serialization, and
    replies are written without an encode-and-parse round trip. In the Views
    benchmarks, 1,000 property changes on a 200-row model take 0.17 ms instead
    of 38 ms. One canonical JSON form is shared by change detection, operation
    digests and interaction reply signatures; an interaction reply with a
    duplicate member name is now invalid-request.
  • Fix: a router that calls history.pushState or replaceState before the
    first Bridge call, such as SvelteKit's client start, no longer loses the
    update when WebUI cancels navigations (#42). Both host client scripts allow
    the history update.
  • Incremental collections recover more reliably. A failed snapshot read is
    retried up to four reads in total, with each retried failure reported to
    onBridgeDiagnostic. Frames that arrive during the read are applied after
    it instead of being dropped. A repeated full state with the same revision and
    content no longer notifies subscribers again.
  • A [RunicCollection] with a null row or a null, empty or duplicate key,
    including a key changed in place on a row, is no longer published for the
    client to reject. The route withholds its state, logs
    BridgeCollectionKeysRejected (event 1012) naming the model, field and key,
    and the client reports a failed BridgeError for the route through
    onBridgeDiagnostic while keeping its last state. The first change after
    the keys are fixed publishes a full state. Collection changes never throw,
    so bindings such as DynamicData Bind keep running.
  • A command can declare its expected failure with
    [RunicFailure(typeof(SaveFailure))] on the command property or on its
    CommunityToolkit [RelayCommand] method, and signal it by throwing
    RunicFailureException(new TitleRequired()). The Bridge replies
    domain-failed with the encoded failure instead of rejected or failed,
    in every environment and never with detail. An operation ends with the
    new domain-failed status: a stream keeps the values it published, and the
    failure counts against the window's retention budget like a result. A value
    that is not the declared type, cannot be encoded or is over 4 KiB is still
    reported as failed. The contract fingerprint covers declarations, so
    models without one keep their fingerprint.
  • In @runic-artifex/views, a command that declares a failure resolves a
    BridgeOutcome: { ok: true, value }, or { ok: false, failure } for its
    declared failure, while unexpected failures still reject with BridgeError.
    Operations add outcome(), which waits and resolves the same way, and
    BridgeOperationStatus<TResult, TFailure> is a union discriminated by kind
    that includes domain-failed only for an operation that declares a failure.
    matchCase(failure, handlers) handles every case of a $case union, and
    bridgeSuccess, bridgeFailure and isBridgeOutcome build and recognize
    outcomes. The generator emits the declared signatures; see
    Upgrading for the control-flow change.
  • createCommandController (behind useCommand and injectCommand) adds
    failure to its state: the declared failure of the latest run, kept apart
    from the unexpected error.
  • Runic.Application.ReactiveUI and its .Reactive flavor add
    ObserveBridgeExceptions on IHandleObservableErrors. A ReactiveCommand
    also reports a declared RunicFailureException on ThrownExceptions, and
    without a subscriber ReactiveUI's default handler breaks into the debugger
    and throws. The helper ignores declared failures and cancellations, which
    the client already received, and passes every other exception to a callback,
    or logs it as ReactiveCommandFailed (event 1042) named after the command
    expression, such as SaveCommand.
  • The Views wire protocol is version 2 (BridgeProtocol.Version), which adds
    domain-failed. Snapshot replies report "protocol": 2; the version stays
    informational. A client reports a host with another version once through
    onBridgeDiagnostic (code: "protocol") and treats a reply error kind it
    does not know as failed. The
    protocol specification
    has portable fixtures for declared failures.
  • Bridges are registered only per composition. The process-wide registration
    path is removed: Bridge.Register, Bridge.Attach,
    WebUiWindow.AttachBridge, the generated module initializer that called
    Bridge.Register, and the RunicBridgeRegisterGlobally MSBuild property.
    A project that references Runic.Application.Views without a host adapter
    no longer registers its Bridges globally. Set RunicBridgeCompositionType
    to generate a composition and call its AddRunicBridges(). A project that
    constructs the generated <Name>Bridge directly must exclude that code from...
Read more

Runic SDK 0.6.0-preview.1

Pre-release

Choose a tag to compare

@github-actions github-actions released this 05 Oct 10:41
3026ada

Runic SDK 0.6.0-preview.1

This preview adds framework bindings for generated Views clients, a guided
project creator with one configurable starter template, and hardens the Desktop host, platform services and generated
bridges. It contains breaking changes; see Upgrading.

Views

  • @runic-artifex/views is the shared browser runtime for generated clients and
    includes a mock Bridge (@runic-artifex/views/mock) for frontend work without
    .NET. Generated modules import it instead of embedding their own copy.
  • New @runic-artifex/react and @runic-artifex/vue packages provide useView.
    Svelte provides useView in @runic-artifex/svelte/views, and Angular provides
    injectView() in @runic-artifex/angular.
  • Content assigned away and back (Main = b; Main = a;) stays live, and released
    content ViewModels are released when they leave the window instead of when the
    window closes. Window close drains running commands before cancelling them.
  • Views re-read routes and remount after a transport reconnect.
  • The wire protocol is specified in specs/application, and snapshot replies
    report "protocol": 1.
  • Every asynchronous command reports its execution state.
  • The generator reports unsupported bridge types, empty DTOs and colliding wire,
    member and route names as build errors. It no longer deletes hand-written files.
  • The frontend build runs only when its inputs change. Set
    RunicBridgeBuildFrontend=false to skip it.

Templates and tooling

  • dnx Runic.Create@0.6.0-preview.1 creates a project interactively. It asks
    for the frontend, package manager, Window host and ViewModel library, then
    prints the commands that recreate the project. The docs site's
    project creator builds the same
    command in the browser.
  • One runic-app template replaces runic-app-react, runic-app-vue,
    runic-app-svelte and runic-app-angular. Its options are --frontend,
    --package-manager, --host cswebui|desktop and
    --view-models toolkit|reactiveui, so a new project can start on Runic
    Desktop or with ReactiveUI ViewModels.
  • The documented first run works on a new project:
    dotnet new runic-app -n MyApp, cd MyApp, dotnet tool restore,
    dotnet runic dev.
  • Starter templates contain only application code. They use AddRunicViews(),
    ServiceProviderViewLocator and the new CsWebUiWindow<TViewModel> base class.
  • The build installs frontend packages when they are missing. Set
    RunicBridgeInstallFrontend=false to opt out.
  • dotnet runic doctor reports optional tooling as warnings, and dotnet runic
    no longer hangs waiting on a child process's input.

Desktop and platform

  • Bootstrap scripts validate the Host header and fetch metadata. Embedded
    webviews receive the session credential through a document-start script.
  • Media capture is granted only to the presented origin, and generated browser
    profiles are owner-only.
  • The GTK 4 open hang, a dispose-from-callback deadlock and failed WebView2 work
    after window close are fixed.
  • Atomic file saves keep the target's mode, follow symlinks and flush to disk,
    and use File.Replace on Windows. Platforms report SupportsAtomicReplace.
  • An active inhibition no longer blocks portal file dialogs. The Windows clipboard
    retries while another process holds it, and absent GTK 4 clipboard text is null.

Command line, assets and translations

  • ProcessRunner rejects Windows .bat and .cmd targets (RCLI6007,
    RCLI6008) unless allowWindowsBatchFiles is set, and reports pipe drain
    timeouts.
  • Response files keep backslashes literal unless they precede a quote, matching
    the MSVC runtime.
  • Asset archives mark only content-hashed files as immutable, and asset endpoints
    serve HEAD and range requests.
  • Translations support nb and nn plurals and underscore locale tags.
  • Plural, ordinal and exact numeric selection use the number as displayed, as in
    MessageFormat 2: {$n :number minimumFractionDigits=1} selects other for 1
    in English, and percent selects on the value times 100. The generated rules
    now apply every CLDR operand, and Czech (cs) plurals are supported.

Upgrading

Install matching 0.6.0-preview.1 packages from NuGet and npm, add
@runic-artifex/views to the frontend dependencies, and rebuild so the C# bridges
and TypeScript clients regenerate together. The
upgrade guide
lists each change:

  • Create projects with dotnet new runic-app --frontend <name> instead of
    dotnet new runic-app-<name>, and replace --packageManager with
    --package-manager.
  • Generated clients are named <Name>Client; <Name>View remains as a
    deprecated alias.
  • BridgeError and the operation and field-baseline types are imported from
    @runic-artifex/views.
  • Generated state no longer contains revision. DateTimeOffset values keep
    their offset, and outbound dates must be ISO 8601 strings.
  • Generated C# files are named <FullName>.Bridge.g.cs and <FullName>.View.g.cs.
  • The ReactiveUI adapters require ReactiveUI 26.0.1 (or ReactiveUI.Reactive
    26.0.1) with Binding 9.1.0; update explicit pins of those packages. The
    ReactiveUI API is unchanged from 25, but Primitives 9 binds a lone
    SubscribeSafe lambda to onNext: rename error-only calls to
    SubscribeSafeErrors.
  • Runic.Platform.Administration.Windows requires an explicit service account,
    a quoted service path, an explicit firewall scope and a share security
    descriptor. New inbound allow rules default to the Domain and Private profiles.
  • Translated messages that select on :number with fraction digit options,
    style=percent or values beyond the displayed six fraction digits can choose
    a different variant. Check their one variants, and change exact keys on
    percent selectors to the displayed value (100 instead of 1).
  • @runic-artifex/sveltekit requires SvelteKit 3 (@sveltejs/kit >=3 <4),
    @sveltejs/adapter-static 4, Svelte 5.57.1 or later and Vite 8.0.12 or later.
    Follow the SvelteKit 3 migration guide
    and pass runicToolkitAdapter(...) and router to sveltekit() in
    vite.config.ts. RunicLocaleCookieOptions now derives from SvelteKit's
    cookie options, and the package no longer depends on @types/cookie.
  • The optional @vitejs/devtools peer of @runic-artifex/vite-plugin-runic is
    ^0.7.6, which requires Vite 8.3 or later.

Known limitations

  • Template acceptance builds and type-checks the Runic Desktop variants but does
    not start them, because the Desktop host has no serve-only mode for headless
    checks.
  • Windows and macOS host changes are compiled and unit-tested but have not yet
    been exercised on those platforms.

Full Changelog: v0.5.0-preview.3...v0.6.0-preview.1

Runic SDK 0.5.0-preview.3

Pre-release

Choose a tag to compare

@github-actions github-actions released this 01 Oct 07:41
29265e1

Runic SDK 0.5.0-preview.3

This preview upgrades the ReactiveUI integration to ReactiveUI 25 and expands
generated Window/View bindings across commands, interactions, data, and validation.

  • The default Runic.Application.ReactiveUI adapter uses ReactiveUI 25.0.1's
    Primitives distribution. The new optional Runic.Application.ReactiveUI.Reactive
    adapter supports the System.Reactive distribution.
  • Generated clients support typed command inputs and results, recoverable async
    operations, bounded result streams, and presentation-owned interactions.
    CommunityToolkit generic commands use the same input codecs and parameter-aware
    CanExecute queries.
  • Checked writes decode exact values in receipts, reject request-ID reuse with a
    different payload, and bound retained receipts by count and encoded bytes.
  • Nested INotifyDataErrorInfo validation exposes structured field paths, codes,
    and severity. Snapshot batching reduces captures during synchronous bulk updates;
    the Translations Editor uses it during document reconciliation.
  • The bridge generator caches unchanged inputs and verifies generated output
    contents before reusing them. Generated clients compile with Angular's strict
    index-signature settings. Editor repair saves reload authoritative workspace state.

Upgrading

Install matching 0.5.0-preview.3 Runic packages from NuGet and npm and rebuild
the application to regenerate its C# bridges and TypeScript clients together.
Use one ReactiveUI distribution consistently:

  • ReactiveUI with Runic.Application.ReactiveUI for the default Primitives APIs.
  • ReactiveUI.Reactive with Runic.Application.ReactiveUI.Reactive for
    System.Reactive APIs. The types and namespaces differ between distributions.

ReactiveUI 25 moved binding and activation APIs into its Binding packages. Follow
the ReactiveUI integration guide
for namespace changes and context-backed schedulers. Keep model updates on the
application's IRunicModelContext and pass its adapter scheduler to command
factories. Generated Int64, UInt64, and BigInteger values are JavaScript bigint;
decimal and date/time values retain their documented string representation.

Known limitations

  • CommunityToolkit cancellation targets the command's current execution and
    does not isolate concurrent invocations of the same command instance.
  • Native AOT generation still rejects CommunityToolkit ObservableValidator
    models. Structured validation projection through generated metadata is AOT-safe.
  • Validation and operation retention are bounded; clients must handle truncation,
    expiration, and uncertain completion by reconciling authoritative state.

What's Changed

Full Changelog: v0.5.0-preview.2...v0.5.0-preview.3

Runic SDK 0.5.0-preview.2

Pre-release

Choose a tag to compare

@github-actions github-actions released this 25 Sep 21:40
1870bc3

Runic SDK 0.5.0-preview.2

Runic Next introduces typed Window and View contracts, generated TypeScript clients, scoped ViewModel lifetimes, and Runic Desktop hosting. The Translations Editor now uses routed ReactiveUI ViewModels and typed command results. This is a breaking preview for applications built on the previous Application Bridge API.

Install

Install matching 0.5.0-preview.2 Runic packages from NuGet and npm. To start a Svelte application:

dotnet new install Runic.Application.Templates::0.5.0-preview.2
dotnet new runic-app-svelte --name MyApp --packageManager bun
cd MyApp
dotnet tool restore
dotnet run

See the application guide for other frontend templates and host setup.

What changed

  • Runic.Application owns the Window/View model. Runic.Application.CsWebUi provides the scoped window factory, while Runic.Application.Desktop hosts Views through Runic Desktop.
  • Runic.Application.ReactiveUI is an optional integration for ReactiveUI ViewModels and routing.
  • Runic.Application.Testing now tests generated routes, snapshots, publications, and mounted View lifetimes without opening a native window.
  • Svelte's View outlet is available from @runic-artifex/svelte/views; Angular's outlet is in @runic-artifex/angular.
  • The Translations Editor uses routed ReactiveUI feature and document ViewModels. The editor remains available from source; this release does not include a standalone editor distribution.

Migration

Replace the retired NuGet packages Runic.Application.Bridge, Runic.Application.Hosting, Runic.Application.Platform, and Runic.Application.Platform.Desktop. The npm packages @runic-artifex/application-bridge, @runic-artifex/application-bridge-tooling, and @runic-artifex/desktop are also retired. The retained Runic.Application, Runic.Application.CsWebUi, Runic.Application.Desktop, and Runic.Application.Testing package IDs now expose the Window/View APIs, so rebuild generated clients and update source references when upgrading from 0.4.0-preview.1. Phase 1's Runic.Application.Views package IDs were never published. C# View namespaces remain unchanged in this preview.

Changes by PR

Full changelog: 0.4.0-preview.1...0.5.0-preview.2

Runic SDK 0.4.0-preview.1

Pre-release

Choose a tag to compare

@github-actions github-actions released this 22 Sep 16:04
25c8895

Install matching 0.4.0-preview.1 packages from NuGet and npm. See the repository README for getting started.

What's Changed

New Contributors

Full Changelog: v0.3.0-preview.1...v0.4.0-preview.1