Repository navigation
Releases: Runic-Artifex/runic-sdk
Release list
Runic SDK 0.7.0-preview.6
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.OpenDirectoryAsyncreturns an ownedIDirectoryLeasewith 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.
Theplatform.directories.opencapability 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
createOperationControllerand Svelte'suseOperationexpose Start admission,
pending state, terminal outcomes and cancellation feedback. Observation
disposal detaches the UI; it does not cancel accepted application mutations.createLatestOperationControllercoalesces 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.AcceptedWorkScopereserves 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.
WindowContentSessioncompletion 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 unconditionalJsonIgnoreDTO 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,WhenWritingNullandWhenWritingDefaultremain bridge members. - Constructor diagnostics explain the difference between serializer exclusion
and bridge exclusion and point to the opt-in policy orRunicIgnore. - Creator documentation uses
--directoryfor the destination. Its global
--outputoption 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
- Record SDK preview.5 API baseline and start preview.6 by @ViktorJannicke in #155
- Improve external app directory access, operations and shutdown by @ViktorJannicke in #156
- Fix cancelled departure test to await dialog cleanup by @ViktorJannicke in #157
Full Changelog: v0.7.0-preview.5...v0.7.0-preview.6
Runic SDK 0.7.0-preview.5
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.Wpfpackage embeds a child WebView2 presentation
in an existing WPF shell on .NET 10.RunicWebViewandCreateWpfViewAsync
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. RunicModelContextnow exposes itsClosedtoken through
IRunicModelContextLifetime, matching the WPF dispatcher context. Its
navigator starts closing immediately and rejects later requests asClosed,
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.Regionconnects single selection to borrowed Replace
requests, including typed regions. It restores selection after rejection and
cancels pending selection on unload. ATabControlreceives a default
NavigationHostcontent template when the application supplied none.ViewHostlocates 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
IncludeCancelCommandcommands are supported by
the bridge generator and execute the actual Toolkit cancel command. Arbitrary
ICommandmembers 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
- Fix joined Back cancellation and web model-context shutdown by @ViktorJannicke in #149
- Bring verified idiomatic navigation integrations onto main by @ViktorJannicke in #151
- Add WPF selectors and plain-content view hosting over shared navigation by @ViktorJannicke in #152
- Host Runic web Views inside an existing WPF shell by @ViktorJannicke in #153
- Prepare SDK 0.7.0-preview.5 migration release by @ViktorJannicke in #154
Full Changelog: v0.7.0-preview.4...v0.7.0-preview.5
Runic SDK 0.7.0-preview.4
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.Navigationis now its own package, andRunic.Navigation.Wpfadds a
NavigationHost, a dialog host and a dispatcher-backed model context.- The ReactiveUI helpers moved to
Runic.Navigation.ReactiveUIand
Runic.Navigation.ReactiveUI.Reactive. - Navigation gains container-built targets (
PushAsync<T>()), per-entry
service scopes,LeaveConfirmationandDismissAsync. - 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.RUNICBRIDGE006remains an error when the project sets
RunicBridgeBuildEnabledtotrue, has a frontendpackage.json, declares
RunicBridgeFrontendInputitems, or setsRunicBridgeCompositionTypeor
RunicBridgeModelAssembly. Projects that reference a host adapter
(Runic.Application.CsWebUiorRunic.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 andModelContext,
WindowContentSessionOptions.ModelContext, theRunicWindowTestHost<TViewModel>
constructor andRunicWindowTestHostOptions.ModelContext
(Runic.Application.Testing). The ReactiveUI helpers moved to their own
packages, listed below. - Log categories:
RunicModelContextlogs 1030-1033 through
ILogger<RunicModelContext>, so its category changes from
Runic.Application.Views.RunicModelContextto
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 underRunic.Application.Views. - Generated bridges don't name navigation types, so generated code needs no
newusing. The packed generator carriesRunic.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,
BridgeSnapshotObservableExtensionsandRunicReactiveExceptionsstay in the
Application adapters and their namespaces.ModelSchedulerregistration.AddRunicReactiveModelContextregisters
ISequencer(ISchedulerin 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 underValidateScopes. A custom
IRunicReactiveSchedulerProvideris called once per resolution instead of once
per scope. With a scoped model context, a singleton ViewModel that injects
ISequencer/ISchedulernow fails at resolve time instead of at
ValidateOnBuild, because the transient factory hides the scoped dependency.- Flavor guard.
Runic.Application.ReactiveUI.Reactivealso rejects a direct
reference toRunic.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.
DispatcherModelContextruns model turns on a WPFDispatcherand
implementsIRunicModelHookScheduler, 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.NavigationHostpresents a region's current entry with a new presenter and
View per entry. Views come from the host'sINavigationViewLocator, the
registered locator (MapView,UseViewNamingConvention) or the implicit
DataTemplate. It handlesNavigationCommands.BrowseBack. A locator View's
constructor parameter typed as the content's class or a base class, never
objector an interface, gets the entry's content.
UseViewNamingConvention(params Assembly[])also searches View assemblies
apart from the ViewModels'.NavigationDialogHostshows each entry of a region in an owned window with
Show(), neverShowDialog(). It disables the thread's other windows
(Modality="Application") or the owner chain (Owner), turns a u...
Runic SDK 0.7.0-preview.3
Runic SDK 0.7.0-preview.3
Views
WindowContentSession.Forgetis now a no-op after the session is disposed, like
ClearOwner; it threwObjectDisposedExceptionbefore. 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
DesktopNativeOwnerinRunic.Application.Desktopis a shipped
INativePickerOwnerfor an embedded Desktop window, and
DesktopBridgeWindow<TViewModel>.NativeOwnerexposes 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 onRunic.Platform.Runtime.DesktopHostOptions.WithGtk4()inRunic.Desktop.Gtk4applies the GTK 4
profile: it selectsLinuxEmbeddedBackend.Gtk4WebKit6and sets
WindowHostFactorytoGtk4WindowHostFactorytogether. On Windows and macOS
it keeps the platform host. It throwsArgumentExceptionwhen another factory
is already set.- Availability reports every missing embedded prerequisite, not only the first.
DesktopPresentationAvailability.Diagnosticsand
DesktopPresentationPreflight.Diagnosticslist them;Diagnosticstays the
first entry, and settingDiagnostic(for example withwith) replaces the
list with that one diagnostic. Availabilities compare their diagnostics by
value, so two identicalGetAvailability()results are equal.
DesktopHost.ValidateandDesktopAvailabilityResult.Diagnostics
now include all of them. A GTK4 selection without its provider lists
gtk4-provider-missing, the newgtk4-runtime-missing,
webkitgtk6-runtime-missingandgraphical-session-missingtogether; a GTK3
selection listswebkitgtk-runtime-missingandgraphical-session-missing
together. Code that countedValidateerrors may now see more than one. IDesktopWindowHostFactory.GetAvailabilityDiagnostics()(default: none) lets
an unavailable factory replace the genericcustom-window-host-unavailable
diagnostic with its own list; a code the host already reports, such as a
toolkit conflict, is listed once.Gtk4WindowHostFactoryreports each missing
library and a toolkit conflict.LinuxDesktopRuntime.IsLibraryAvailablecaches each definite result for the
process lifetime and keeps the ldconfig cache once it has read it, so
availability checks no longer start anldconfigprocess per missing library.
A failed or timed-outldconfigread 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 aDesktopHost, runs the application on the
loop its window host needs, disposes the host and returns the application's
exit code. A supportedIDesktopEventLoopWindowHostFactory, such as the GTK 4
factory fromWithGtk4(), 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 toDiagnosticSinkand the logger, and a browser
fallback still works. For example, GTK 4 opens its display with
gtk_init_checkand reportsgtk4-display-unavailableinstead of exiting the
process. A nestedDesktopEventLoop.Runthrows on every platform.Gtk4WindowHostFactory.ApplicationIdand
WithGtk4(applicationId)set the GTK application ID.- The
runic-apptemplate has adesktop-gtk4host (--host desktop-gtk4,
also offered byRunic.Create). It selects GTK 4 and WebKitGTK 6 on Linux
withWithGtk4()and referencesRunic.Desktop.Gtk4,
Runic.Platform.Linux.Gtk4andRunic.Platform.Linux.Portal. Both Desktop
hosts now start withDesktopEventLoop.Run(options, ...)and write Desktop
diagnostics, such as an embedded-to-browser fallback, to standard error. On
Windows and macOSdesktop-gtk4behaves likedesktop. RUNIC_APPLICATION_CLOSE_AFTER_OPEN=1makesOpenDesktopWindowAsyncclose
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 thedesktop-gtk4variant under
Xvfb (RUNIC_TEMPLATE_DESKTOP_SMOKE=1).dotnet runic doctoradds agtk4-profilecheck 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-releasewhen 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 restoresRunic.Platform.Linux,
whose GTK 3 portal parent loads GTK 3 into the GTK 4 process. Doctor and the
runtime accept bothlibwebkitgtk-6.0.so.4andlibwebkitgtk-6.0.so.0.
Experimental
- Typed navigation (#61):
RunicNavigator,NavigationRegion<TContent>and
NavigationTargetinRunic.Applicationgive 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
RUNICNAV001to use it and expect changes before it is supported. Navigation
logs events 1060-1071 underRunic.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
aNavigationResultRequestwhoseCompletionendsCompleted(value)when the
entry callsNavigationEntryContext.CompleteAsync(value)and the Back it issues
commits, andDismissedon 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
CompleteAsyncor caller cancellation may leave the region empty, so a confirm
can be pushed into an empty dialog region; a plainBackAsyncfrom a single
entry is stillNoHistory. 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 laterCompleteAsyncdrops the value.CompleteAsyncaccepts
any value of the result type at run time, sotruecompletes abool?request
and5anobjectrequest. 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'sCurrent. Its generated TypeScript type, the
union of the presentablePageReferencetypes, always includes| null,
so moving a non-null content property to a region adds| nulland 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 newCurrent; 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.TestingaddsUnretiredEntryCount()and
RetainedContentModelCount()for navigation lifecycle tests.Runic.Application.ReactiveUIandRunic.Application.ReactiveUI.Reactiveadd
WhenCurrentChanged(),WhenEntryChanged()andCreateBackCommand(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 theNavigationResult; 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
andDialogare regions, and the shell presents theirCurrentas| null
slots. Each visit to the notes pushes a new owned document with a
CurrentPanechild region. The window-scoped editor keeps the draft. The
unsaved-edits confirm is a departure guard that awaitsPushForResult<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
- Point README guide links at the docs site by @ViktorJannicke in https://github.com/Runic-Artifex/runic-sdk/pull...
Runic SDK 0.7.0-preview.2
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 devis 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 becomingRCLI5000.
Desktop
- A browser window whose browser starts but requests nothing within
ConnectionTimeoutis 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.BrowserLaunchAttemptsbounds 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
reportsbrowser-launch-stalledto 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 untilConnectionTimeout; 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.Windowswere 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.AdminVerifydiagnostic tool:- follows the Runic command-line conventions: per-command
--help,
--version,--output jsonandcompletion; - 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
localanddomainoptions and the exit codes are unchanged. - follows the Runic command-line conventions: per-command
Supported platforms
eng/support.jsonnow 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 forRunic.Desktop.Gtk4). The README's
Supported platforms
table is generated from it.dotnet runic doctor --ridreads 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 toschemaVersion2 and gains a
supportobject. Existing properties are unchanged. The tool reads only the
copy embedded in itself.
What's Changed
- Ship 0.7.0-preview.1 public API and start 0.7.0-preview.2 (W130-054) by @ViktorJannicke in #106
- Planning: W130-055 by @ViktorJannicke in #105
- Platform: record Windows NativeAOT interactive evidence (W120-020) by @ViktorJannicke in #102
- Planning: W130-058 by @ViktorJannicke in #109
- Planning: W130-059 by @ViktorJannicke in #108
- dotnet-runic: keep dev cleanup on a repeated signal; CLI 0.6.0-preview.2 notes (W130-054) by @ViktorJannicke in #107
- Administration verifier: CLI conventions, denied-access mode and local mutation acceptance (W120-019) by @ViktorJannicke in #103
- Remove the deprecated Linux VM test helpers by @ViktorJannicke in #111
- Generate the support matrix from eng/support.json (W130-048) by @ViktorJannicke in #112
- CI: time-limit and retry the Playwright browser install by @ViktorJannicke in #113
- Close silent Bridge WebSockets and describe stalled handshakes (W130-063) by @ViktorJannicke in #116
Full Changelog: v0.7.0-preview.1...v0.7.0-preview.2
Runic SDK 0.7.0-preview.1
Runic SDK 0.7.0-preview.1
This preview contains breaking changes; see Upgrading.
Assets
AssetManifest.TryGetAssetreturnsfalsefor an invalid path instead of
throwing. The newAssetPath.TryNormalizevalidates 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
ArgumentExceptionat run time. - Both adapters resolve request paths with the new
AssetManifest.TryResolveRequestPathandAssetRoutingOptions, 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 theirRAS1001–RAS1004
code instead ofRCLI5000("The command failed unexpectedly."). Fault
messages no longer contain paths or exception text; the source directory,
entry point and underlying reason are infault.details. Values that look
like home,/tmpor/rootpaths, 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.DesktopandRunic.Application.CsWebUidepend on
Microsoft.Extensions.DependencyInjection.Abstractionsinstead of the
Microsoft.Extensions.DependencyInjectioncontainer.- 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
BridgeErrorkinddisconnected. An operation start whose reply is lost
checks the operation's status, and reportsBridgeOperationUncertainError
when that fails, because .NET may already have started it. Interaction
handlers pause while the connection is down (see Views). Runic.Application.CsWebUiaddsValidateWindow<TViewModel>(), which checks
at startup that the generated Bridge is registered.OpenWindowruns the same
check before it creates the native window, so a missingAddRunicViews()
throws aCsWebUiConfigurationExceptionwithbridge-not-registered, the
code Runic Desktop reports, instead of a dependency-injection error. It
derives fromInvalidOperationExceptionand 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:
BridgeErrorhas
route,causeand, in development,detailwith the .NET exception type,
message and stack. Production replies keep the bounded message. Development
means the host environment isDevelopmentor
BridgeDiagnostics.IncludeFailureDetailistrue. waitForBridge({ timeout })reports a missingwindow.__runicBridge, for
example a frontend opened from a plain Vite server, asunavailablewith
remediation, and a Bridge that does not connect astimeout.operation.wait({ timeout })cancels the operation when the timeout passes
and resolves to atimedOutstatus.onBridgeDiagnostic(listener)observes runtime failures. While serving,
@runic-artifex/vite-plugin-runicforwards 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 indexedadd,remove,replace
andmoveframes 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 DynamicDataBind. 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 nowinvalid-request. - Fix: a router that calls
history.pushStateorreplaceStatebefore 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 afailedBridgeErrorfor the route through
onBridgeDiagnosticwhile keeping its last state. The first change after
the keys are fixed publishes a full state. Collection changes never throw,
so bindings such as DynamicDataBindkeep 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-failedwith the encoded failure instead ofrejectedorfailed,
in every environment and never withdetail. An operation ends with the
newdomain-failedstatus: 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 asfailed. 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 withBridgeError.
Operations addoutcome(), which waits and resolves the same way, and
BridgeOperationStatus<TResult, TFailure>is a union discriminated bykind
that includesdomain-failedonly for an operation that declares a failure.
matchCase(failure, handlers)handles every case of a$caseunion, and
bridgeSuccess,bridgeFailureandisBridgeOutcomebuild and recognize
outcomes. The generator emits the declared signatures; see
Upgrading for the control-flow change. createCommandController(behinduseCommandandinjectCommand) adds
failureto its state: the declared failure of the latest run, kept apart
from the unexpectederror.Runic.Application.ReactiveUIand its.Reactiveflavor add
ObserveBridgeExceptionsonIHandleObservableErrors. AReactiveCommand
also reports a declaredRunicFailureExceptiononThrownExceptions, 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 asReactiveCommandFailed(event 1042) named after the command
expression, such asSaveCommand.- 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 asfailed. 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 theRunicBridgeRegisterGloballyMSBuild property.
A project that referencesRunic.Application.Viewswithout a host adapter
no longer registers its Bridges globally. SetRunicBridgeCompositionType
to generate a composition and call itsAddRunicBridges(). A project that
constructs the generated<Name>Bridgedirectly must exclude that code from...
Runic SDK 0.6.0-preview.1
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/viewsis 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/reactand@runic-artifex/vuepackages provideuseView.
Svelte providesuseViewin@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=falseto skip it.
Templates and tooling
dnx Runic.Create@0.6.0-preview.1creates 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-apptemplate replacesrunic-app-react,runic-app-vue,
runic-app-svelteandrunic-app-angular. Its options are--frontend,
--package-manager,--host cswebui|desktopand
--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(),
ServiceProviderViewLocatorand the newCsWebUiWindow<TViewModel>base class. - The build installs frontend packages when they are missing. Set
RunicBridgeInstallFrontend=falseto opt out. dotnet runic doctorreports optional tooling as warnings, anddotnet 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 useFile.Replaceon Windows. Platforms reportSupportsAtomicReplace. - An active inhibition no longer blocks portal file dialogs. The Windows clipboard
retries while another process holds it, and absent GTK 4 clipboard text isnull.
Command line, assets and translations
ProcessRunnerrejects Windows.batand.cmdtargets (RCLI6007,
RCLI6008) unlessallowWindowsBatchFilesis 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
serveHEADand range requests. - Translations support
nbandnnplurals and underscore locale tags. - Plural, ordinal and exact numeric selection use the number as displayed, as in
MessageFormat 2:{$n :number minimumFractionDigits=1}selectsotherfor 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--packageManagerwith
--package-manager. - Generated clients are named
<Name>Client;<Name>Viewremains as a
deprecated alias. BridgeErrorand the operation and field-baseline types are imported from
@runic-artifex/views.- Generated state no longer contains
revision.DateTimeOffsetvalues keep
their offset, and outbound dates must be ISO 8601 strings. - Generated C# files are named
<FullName>.Bridge.g.csand<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
SubscribeSafelambda toonNext: rename error-only calls to
SubscribeSafeErrors. Runic.Platform.Administration.Windowsrequires 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
:numberwith fraction digit options,
style=percentor values beyond the displayed six fraction digits can choose
a different variant. Check theironevariants, and change exact keys on
percent selectors to the displayed value (100instead of1). @runic-artifex/sveltekitrequires SvelteKit 3 (@sveltejs/kit>=3 <4),
@sveltejs/adapter-static4, Svelte 5.57.1 or later and Vite 8.0.12 or later.
Follow the SvelteKit 3 migration guide
and passrunicToolkitAdapter(...)androutertosveltekit()in
vite.config.ts.RunicLocaleCookieOptionsnow derives from SvelteKit's
cookie options, and the package no longer depends on@types/cookie.- The optional
@vitejs/devtoolspeer of@runic-artifex/vite-plugin-runicis
^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
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.ReactiveUIadapter uses ReactiveUI 25.0.1's
Primitives distribution. The new optionalRunic.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
CanExecutequeries. - 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
INotifyDataErrorInfovalidation 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:
ReactiveUIwithRunic.Application.ReactiveUIfor the default Primitives APIs.ReactiveUI.ReactivewithRunic.Application.ReactiveUI.Reactivefor
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
- Refresh docs for Runic SDK 0.5.0-preview.2 by @ViktorJannicke in #34
Full Changelog: v0.5.0-preview.2...v0.5.0-preview.3
Runic SDK 0.5.0-preview.2
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 runSee the application guide for other frontend templates and host setup.
What changed
Runic.Applicationowns the Window/View model.Runic.Application.CsWebUiprovides the scoped window factory, whileRunic.Application.Desktophosts Views through Runic Desktop.Runic.Application.ReactiveUIis an optional integration for ReactiveUI ViewModels and routing.Runic.Application.Testingnow 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
- Runic Next Phase 1
- Runic Next Phase 2: Desktop Views and package consolidation
- Browser reconnect stabilization
- Routed View collections and editor MVVM migration
- Runic.Application.Testing
- First Window browser smoke stabilization
Full changelog: 0.4.0-preview.1...0.5.0-preview.2
Runic SDK 0.4.0-preview.1
Install matching 0.4.0-preview.1 packages from NuGet and npm. See the repository README for getting started.
What's Changed
- Repair SDK release graph for WPF package by @ViktorJannicke in #2
- fix: complete RMF2 migration and XLIFF interchange by @ViktorJannicke in #3
- RMF2 contract coherence for manifests and capabilities by @ViktorJannicke in #4
- Add staged RMF2 semantic v5 foundation by @ViktorJannicke in #5
- feat(translations): add typed RMF2 v5 .NET runtime execution by @ViktorJannicke in #6
- feat(translations): add staged RMF2 v5 project linker by @ViktorJannicke in #7
- feat(translations): add staged RMF2 v5 locale packs by @ViktorJannicke in #8
- Generate staged RMF2 v5 C# surfaces by @ViktorJannicke in #9
- feat(translations): generate staged RMF2 v5 ESM by @ViktorJannicke in #10
- feat(translations): activate RMF2 execution v2 by @ViktorJannicke in #11
- fix(translations): reconcile RMF2 discovery and watchers by @ViktorJannicke in #12
- test(translations): add RMF2 scaffolds and packaged v5 consumers by @ViktorJannicke in #13
- Close RMF2 renderer and NativeAOT parity by @ViktorJannicke in #14
- Add the shared RMF2 v1 conformance oracle by @ViktorJannicke in #15
- Complete RMF2 IDE evidence and v2 authoring by @ViktorJannicke in #16
- Support RMF2 v2 in the translations editor by @ViktorJannicke in #17
- Reconcile RMF2 release documentation by @ViktorJannicke in #18
- Restore translations capability generation by @ViktorJannicke in #19
- chore(engineering): retire replaced compatibility tooling by @ViktorJannicke in #21
- chore(application): remove obsolete migration compatibility by @ViktorJannicke in #20
- chore(web): remove preview compatibility shims by @ViktorJannicke in #22
- feat(translations): retire locale TOML sources by @ViktorJannicke in #23
- Collapse Runic Translations onto the RMF2 v5 contract by @ViktorJannicke in #24
- Reconcile the reduced SDK surface for release by @ViktorJannicke in #25
- Prepare Runic SDK 0.4.0-preview.1 by @ViktorJannicke in #26
New Contributors
- @ViktorJannicke made their first contribution in #2
Full Changelog: v0.3.0-preview.1...v0.4.0-preview.1