Paperbark 0.2.0-alpha.16.1.c9da1a1
dotnet add package Paperbark --version 0.2.0-alpha.16.1.c9da1a1
NuGet\Install-Package Paperbark -Version 0.2.0-alpha.16.1.c9da1a1
<PackageReference Include="Paperbark" Version="0.2.0-alpha.16.1.c9da1a1" />
<PackageVersion Include="Paperbark" Version="0.2.0-alpha.16.1.c9da1a1" />
<PackageReference Include="Paperbark" />
paket add Paperbark --version 0.2.0-alpha.16.1.c9da1a1
#r "nuget: Paperbark, 0.2.0-alpha.16.1.c9da1a1"
#:package Paperbark@0.2.0-alpha.16.1.c9da1a1
#addin nuget:?package=Paperbark&version=0.2.0-alpha.16.1.c9da1a1&prerelease
#tool nuget:?package=Paperbark&version=0.2.0-alpha.16.1.c9da1a1&prerelease
<p align="center"> <img src="site/public/brand/paperbark-lockup.png" alt="Paperbark — lightweight WebView wrapper for .NET" width="760"> </p>
Paperbark
Paperbark is a small package for turning an ASP.NET Core application into a native desktop application. Add the package, choose the first route, and keep using the hosting, routing, dependency injection, and web stack you already know. Its deliberately minimal API covers the essentials: windows, dialogs, titles, and closing a surface with a result.
This repository is an architecture spike, not a production-ready framework. The native host supports macOS arm64 and x64. A Windows arm64/x64 WebView2 host is available as an architecture spike; Linux remains follow-up work.
Developer experience
Register Paperbark, configure an application-relative startup route, and run the application exactly like any other ASP.NET Core application:
using Paperbark;
var builder = WebApplication.CreateBuilder(args);
builder.AddPaperbark(options =>
{
options.Route = "/desktop";
options.Title = "My application";
options.Width = 1280;
options.Height = 800;
});
var app = builder.Build();
app.MapGet(
"/desktop",
() => Results.Content("<h1>Hello from ASP.NET Core</h1>", "text/html"));
await app.RunAsync();
AddPaperbark binds Kestrel to an ephemeral IPv4 loopback port and installs the
Paperbark HTTP session boundary. A hosted lifecycle service waits until Kestrel
has its actual address, extracts or resolves the native host for the current
runtime, and launches it with a private control channel. Paperbark therefore does
not replace ASP.NET's normal startup or shutdown model.
Native quit confirmation
Confirmation is opt-in and does not depend on browser focus or DOM events:
builder.AddPaperbark(options =>
{
options.CloseConfirmation = new PaperbarkCloseConfirmationOptions
{
Title = "Quit application?",
Message = "Quitting ends the current session and may interrupt running commands."
};
});
The native dialog has Cancel (initial focus, Enter, and Escape) and an explicit
Quit action. On macOS the application menu provides Cmd+Q; application quit
and the primary window's close button share the policy. On Windows Alt+F4 and
the primary close button are intercepted by FormClosing, before WebView disposal.
Only confirmed quit ends the host and stops ASP.NET Core. Repeated quit requests
cannot stack confirmations; cancel leaves the session alive and restores focus.
Closing a secondary window or modal dialog only dismisses that surface. With an application modal open, macOS Cmd+Q presents confirmation above that sheet; Windows Alt+F4 on the modal dismisses the modal, not the application. A primary close request on Windows can present confirmation owned by the active modal. Cancel preserves the modal and its pending result. New window/dialog requests are rejected while quit confirmation is pending.
CloseConfirmation defaults to null, preserving existing close behavior.
Title and message must be nonblank. The configuration travels in the authenticated
initialize.closeConfirmation object (title and message), not on the command
line. Use a matching native host build; an older NativeHostPath override cannot
enforce this option. Explicit managed shutdown, control-channel loss, parent loss,
startup cleanup, and native smoke-test completion bypass interactive confirmation.
Windows system shutdown also bypasses confirmation. MainWindow.CloseAsync is a
user-facing close request and observes the policy; its completion acknowledges the
request, not the user's eventual decision. Linux native hosting is not implemented.
Await application work before native close
PaperbarkOptions.CloseHandler is an opt-in asynchronous decision policy on
macOS and Windows. It runs after the configured native confirmation, off the
control reader. Cancel never calls it. Returning approval authorizes closure;
it is not an observation of closure. Keep CloseConfirmation configured when
work needs an explicit Cancel/Quit decision:
builder.AddPaperbark(options =>
{
options.CloseConfirmation = new()
{
Title = "Quit application?",
Message = "Save the session state and quit?"
};
options.CloseDecisionTimeout = TimeSpan.FromSeconds(30);
options.CloseHandler = async (request, cancellationToken) =>
{
// sessionStore is an application-owned service. Key durable work by request.Id.
var result = await sessionStore.CommitCloseAsync(
request.Id, request.WindowId, cancellationToken);
if (!result.Succeeded)
{
await notifications.ShowCloseFailureAsync(result.Error);
return PaperbarkCloseDecision.Veto;
}
return PaperbarkCloseDecision.Approve;
};
});
sessionStore and notifications above stand for application services, not
Paperbark APIs. The handler may use DI via services.AddOptions<PaperbarkOptions>() .Configure<YourService>((options, service) => ...). Await all required durable
work before returning. A veto, exception, or cancellation does not undo work
already performed; an ended session cannot be resurrected by keeping its
window open. Use application UI to report partial outcomes.
The immutable PaperbarkCloseRequest contains Id, WindowId, Origin
(TitleBar, ApplicationQuit, Managed, Browser) and Scope (Window,
Application). With policy enabled, primary close is application quit, not an
independent primary-window lifetime. Secondary close is window-scoped and uses
the handler without the application Quit prompt. Modal dialogs retain their
existing result/dismissal behavior and do not invoke application close policy;
a handler can open and dismiss an actionable dialog without deadlocking itself.
Application Quit while a non-dialog close is pending is rejected, not silently
upgraded into a different scope.
Observe all origins, including native close-button failures, through the desktop:
var desktop = app.Services.GetRequiredService<IPaperbarkDesktop>();
desktop.CloseStarted += (_, operation) => closeOperations.Track(operation);
desktop.CloseCompleted += (_, result) => notifications.Publish(result);
var operation = await desktop.MainWindow.RequestCloseAsync();
var result = await operation.Completion; // Not the command acknowledgement.
closeOperations and notifications are application-owned state/UI services.
Subscribe before running the application. Event subscribers run serially off the
control reader and must return promptly; throwing subscribers are logged without
suppressing other subscribers or operation completion. Async-void subscribers are
not awaited. Never await an operation's Completion from its own handler:
native closure follows the handler's returned approval. The decision context
deliberately has no completion task.
Ordering is: CloseStarted is queued, native confirmation, handler (if enabled),
decision, actual native window closure, Completion/CloseCompleted.
Cancellation instead completes Cancelled before handler delivery.
CloseStarted observers are notifications, not a blocking policy phase.
IPaperbarkWindow.Closed separately observes actual window closure; veto/cancel
does not finish it. Final native frames and synchronous observer invocations are
drained before process-exit-triggered managed shutdown (two-second bounds).
A slow subscriber cannot hold shutdown indefinitely; use Completion for the
authoritative result and do durable work in the decision handler, not observers.
| Outcome | Meaning |
|---|---|
Closed |
Native window(s) actually closed; never inferred from approval or exit code. |
Cancelled |
User cancelled native confirmation; application handler was not called. |
Vetoed |
Handler declined; window remains open. Effects are not rolled back. |
Failed |
Handler exception/cancellation/deadline or native decision deadline; window stays open and close is quarantined. |
Busy |
Another non-dialog operation or unreconciled failure blocks this new intent; no policy effects for it. |
Bypassed |
Managed/system teardown bypassed policy; not deliberate consent or proof of rollback. |
Unavailable |
Stale window, transport loss, native crash, or missing definitive result; closure is unknown. |
There is one active non-dialog transaction. Concurrent/reentrant requests are
rejected as Busy, and repeated native gestures cannot stack prompts or execute
policy twice. Cancelling RequestCloseAsync's token after delivery only abandons
that caller's wait: it does not withdraw native intent. Subscribe to
CloseStarted to retain the operation independently. A fresh intent after
Cancel/veto gets a fresh ID. Late or mismatched decisions cannot approve a
different request.
Handler deadlines are 100 milliseconds to five minutes (default 30 seconds).
After failure, the operation retains its ID and final Completion.
HandlerCompletion independently reports when application code really finishes;
it remains pending if code ignores cancellation. Late approval/veto is recorded
there but never closes the window. After reconciling partial durable effects,
the application can explicitly release quarantine:
var failure = await operation.Completion;
if (failure.Outcome == PaperbarkCloseOutcome.Failed)
{
var eventualHandlerResult = await operation.HandlerCompletion;
await sessionStore.ReconcileAsync(operation.Request.Id, eventualHandlerResult);
await operation.ReconcileFailureAsync();
// Still open. Only a new explicit user intent can start another close.
}
Recovery rejects while the handler is pending and requires the exact quarantined operation. It acknowledges the application's reconciliation; Paperbark cannot verify external durable state. It does not retry effects, reopen a session, or request a close. If the handler never returns, keep the actionable UI available; explicit managed shutdown remains an independent bypass, not successful recovery.
No-handler consumers keep native-only policy and existing confirmation defaults.
The legacy macOS unconfirmed title-bar primary close retains its window-only
behavior while secondary windows exist; programmatic primary close and explicit
Quit remain application-scoped. Opting into CloseHandler makes primary close
application-scoped consistently on both platforms. Existing CloseAsync and
browser close() remain acknowledgement APIs but enter the same transaction.
Custom implementations of the public interfaces must implement the additive
members. A matching native build is required: initialization rejects an opt-in
handler if the host does not advertise close protocol version 1.
Managed shutdown, system termination, parent loss, and bridge/control loss do not invoke user policy. Pending operations resolve as bypassed/unavailable rather than fabricating consent from a PID, process exit, socket count, DOM unload, or log. No framework can promise delivery after abrupt termination of the managed process.
Managed services can control native windows through DI:
app.MapPost(
"/window",
async (IPaperbarkDesktop desktop, CancellationToken cancellationToken) =>
{
await desktop.OpenWindowAsync(
new PaperbarkWindowOptions
{
Route = "/dialog",
Title = "Application dialog",
Width = 680,
Height = 440
},
cancellationToken);
return Results.NoContent();
});
Client-side desktop services
Paperbark carries a small JavaScript library as an embedded resource. The managed
process transfers it to the authenticated native host over the control channel.
The host injects it into the main frame at document start with WKUserScript;
application files do not need to reference a script URL.
const windowHandle = await window.paperbark.openWindow({
route: "/dialog",
title: "Opened from the SPA",
width: 680,
height: 440
});
await windowHandle.setTitle("Updated by the SPA");
Modal dialogs are async AppKit sheets attached to the primary window:
const result = await window.paperbark.showDialog({
route: "/confirm",
title: "Confirm operation",
width: 640,
height: 420
});
console.log(result);
The backend-served dialog content completes that promise through its own injected window API:
await window.paperbark.window.close("accepted");
The additive browser requestClose(value) uses the same native policy and returns
{ id, waitForCompletion(), waitForHandler(), reconcileFailure() }. The wait
methods return structured objects with string enum names matching the managed
API. Completed operation lookup retains the latest 128 ordinary operations;
active/failed operations remain available until recovery or host teardown.
Browser calls cannot promise to resolve after their own WebView is destroyed:
use managed CloseCompleted/Completion for application-quit observation. A
surviving page may observe another window's close; never rely on beforeunload
for durable work.
The injected facade sends a private WebView postMessage envelope to the native
shell. The shell supplies the trusted source window ID and forwards a framed
bridge-request to ASP.NET, where a fixed DI-backed dispatcher validates and
handles the method. A handler can issue another native command over the same
connection before returning a bridge-response; both read loops remain
re-entrant so modal operations do not deadlock.
The public API is deliberately constrained to paperbark.window,
paperbark.openWindow, and paperbark.showDialog. There is no public generic
invoke, custom handler registration, or shell extension surface. Ordinary
windows are independent NSWindow instances. Modal dialogs use an AppKit sheet:
the primary window is blocked and the JavaScript promise remains pending until
dialog content closes the sheet. All WebViews share the same nonpersistent HTTP
session, injected facade, and restricted navigation origin.
Samples
Four samples exercise different web application styles:
| Sample | UI stack | Desktop bridge |
|---|---|---|
samples/minimal |
Minimal ASP.NET Core HTML | Calls the injected API directly |
samples/asteroids |
High-DPI Canvas game | Native-window game loop and keyboard input |
samples/react |
React 19 + TypeScript + Vite | Calls window.paperbark directly |
samples/blazor |
Interactive Server Blazor | Calls window.paperbark through IJSRuntime |
The Blazor modal route is itself an interactive server component. Its button
event runs on the Blazor circuit and calls the injected
paperbark.window.close(value) API through IJSRuntime, returning the value to
the component that opened the native sheet.
Run them with:
dotnet run --project samples/minimal
dotnet run --project samples/asteroids
dotnet run --project samples/react
dotnet run --project samples/blazor
The React project runs npm ci when its lockfile changes and produces its Vite
bundle during dotnet build. Node.js and npm are therefore additional
prerequisites for building that sample.
Documentation site
The GitHub Pages site under site/ contains the three-step quickstart and
copyable recipes for window management, dialogs, Blazor, publishing, and app
bundle metadata. Run it locally with:
cd site
npm ci
npm run dev
npm run build produces a repository-name-independent static site under
site/dist. The Pages workflow builds pull requests and deploys main through
GitHub's Pages artifact and deployment actions.
Reusable brand assets live under site/public/brand: the original concept
artwork, horizontal lockup, square application mark, and favicon.
Architecture
+---------------- managed ASP.NET Core process ------------------+
| Kestrel on 127.0.0.1:ephemeral |
| capability-cookie middleware |
| routes + SPA/Blazor + application DI |
| fixed DI bridge dispatcher + IPaperbarkDesktop |
| re-entrant framed JSON control server |
+-----------------------------+----------------------------------+
| private UDS or current-user named pipe
| 4-byte big-endian length + JSON
+-----------------------------v----------------------------------+
| native paperbark-host process |
| authenticated bootstrap + WebView message bridge |
| nonpersistent cookie store + injected window.paperbark |
| AppKit/WKWebView or WinForms/WebView2 command dispatcher |
+----------------------------------------------------------------+
The control protocol uses UTF-8 JSON framed by a four-byte network-order payload
length with a 1 MiB limit. The host authenticates its first hello frame with a
random per-launch 256-bit capability. Events currently include ready,
navigated, bridge-tested, host-closing, close-started, close-requested,
close-completed, and window-closed; request/response commands
include initialize, set-title, open-window, show-dialog,
close-window, request-close, recover-close, and shutdown.
close-decision replies carry the exact close ID, target, and scope. Close IDs
are distinct from command acknowledgement IDs. The shell wraps browser calls as
bridge-request messages and ASP.NET returns bridge-response messages with a
result or structured error. Concurrent requests are correlated by a random
request ID. A modal show-dialog request deliberately remains outstanding
until its window is closed with a result.
Initial URL, HTTP capability, cookie name, JavaScript source, and title are sent only after control-channel authentication. They are not passed on the host command line.
The managed process owns the native child. Managed shutdown requests native shutdown and forcibly terminates an unresponsive child after a timeout. Closing the last native window exits the host and stops ASP.NET Core; with confirmation enabled, confirmed primary close exits the entire application, including secondary windows. The native host also monitors the managed parent PID.
Loopback HTTP security
Loopback is treated as transport, not authentication:
- Kestrel binds only to
127.0.0.1on an ephemeral port. - Every run creates a separate random 256-bit HTTP capability.
- The host installs it as a host-only,
HttpOnly,SameSite=Strict, nonpersistent cookie before first navigation. - An early startup-filter middleware rejects every request without an exact host and constant-time capability match.
- Cross-site
Originand Fetch Metadata requests are rejected. - Every WebView uses a nonpersistent website data store.
- WKWebView navigation is restricted to the assigned loopback origin.
- Browser-to-desktop calls use WebView messaging rather than HTTP endpoints; the shell adds the trusted source window identity before forwarding them.
Plain HTTP cannot use a Secure cookie or the __Host- prefix. Another process
running as the same user can also inspect process state or interfere with local
resources. Production hardening should move the remaining control bootstrap
capability away from process arguments, evaluate loopback HTTPS or IPC-backed
HTTP, define CSP and download/external-link policies, and threat-model
same-user compromise explicitly.
Publishing a macOS application
Publishing for a macOS runtime identifier creates a conventional .app bundle
in addition to the loose .NET publish output:
dotnet publish samples/react \
-c Release \
-r osx-arm64 \
-p:PublishAot=true
publish/Paperbark.React.app/
Contents/
Info.plist
MacOS/
Paperbark.React
Paperbark.React.bin
Paperbark.React.staticwebassets.endpoints.json
Resources/app/
appsettings.json
wwwroot/
Paperbark.React is a minimal RID-specific launcher supplied by the NuGet
package. It changes the working directory to Contents/Resources/app and uses
exec to replace itself with Paperbark.React.bin, so it doesn't introduce a
third long-lived process. ASP.NET consequently gets a deterministic content
root even when Finder or Launch Services starts the application. Paperbark also
points the web-root file provider at the bundled wwwroot.
The bundle consumes the final ASP.NET publish output rather than source
directories. React/Vite output, Blazor framework files, scoped CSS, transitive
static web assets, fingerprints, and precompressed files are therefore placed
under the read-only resource root. The endpoint manifest remains beside the
managed executable, where MapStaticAssets expects it.
The buildTransitive/Paperbark.targets file makes this part of an ordinary
dotnet publish; no macOS workload or MAUI dependency is required. Available
MSBuild properties include:
| Property | Default |
|---|---|
PaperbarkCreateAppBundle |
true for osx-* publishes |
PaperbarkAppBundleName |
$(AssemblyName) |
PaperbarkBundleDisplayName |
app bundle name |
PaperbarkBundleIdentifier |
dev.paperbark.$(AssemblyName) |
PaperbarkBundleVersion |
$(VersionPrefix), then 1.0.0 |
PaperbarkMinimumMacOSVersion |
13.0 |
Set PaperbarkCreateAppBundle=false to retain only the ordinary loose publish
layout. The spike generates a valid but unsigned bundle. Production release
work still needs icons, entitlements, hardened-runtime signing, notarization,
and a DMG or ZIP distribution step.
Embedded native host and Native AOT
The macOS build compiles the transparent Swift source in
native/macos/Paperbark.Host/main.swift and embeds the resulting RID-specific
payload into the Paperbark assembly. The same payload is also packed as a
normal NuGet runtime asset.
At startup, Paperbark:
- Selects the embedded resource for the current RID.
- Computes its SHA-256 content hash.
- Acquires a per-payload extraction lock.
- Reuses a cached executable only when its bytes match.
- Otherwise writes and flushes a temporary file, validates it, applies user-only permissions, and atomically moves it into place.
The macOS cache layout is:
~/Library/Caches/Paperbark/hosts/v1/osx-arm64/<sha256>/paperbark-host
Linux uses $XDG_CACHE_HOME/paperbark (or ~/.cache/paperbark) and Windows uses
the user's local application data directory. Protocol version, RID, and content
hash allow applications using identical Paperbark payloads to share the host
without version collisions.
The minimal, React, and Asteroids samples can be published as Native AOT:
dotnet publish samples/minimal \
-c Release \
-r osx-arm64 \
-p:PublishAot=true
The resulting .app contains a Native AOT application executable with the
Paperbark host embedded inside it. The host is extracted on first launch and
reused thereafter. Native AOT applications still need to follow normal ASP.NET
trimming and source-generated serialization constraints. Interactive Server
Blazor currently does not support trimming or Native AOT, so publish the Blazor
sample framework-dependent or self-contained without PublishAot.
Build, test, and package
Prerequisites:
- .NET SDK 10.0
- macOS 13 or later and Xcode Command Line Tools with
swiftc, or Windows with the Evergreen WebView2 Runtime - Node.js and npm for the React sample
dotnet build Paperbark.slnx
dotnet test tests/Paperbark.Tests/Paperbark.Tests.csproj --no-build
dotnet pack src/Paperbark/Paperbark.csproj -c Release
Packing requires both macOS architectures so an incomplete package cannot be published. On macOS they are built automatically. CI builds them once on a macOS runner, then supplies the complete tree to the cross-platform pack job:
scripts/build-macos-host.sh artifacts/native
pwsh scripts/build-windows-host.ps1 artifacts/native
dotnet pack src/Paperbark/Paperbark.csproj -c Release \
-p:PaperbarkNativeAssetsRoot="$PWD/artifacts/native"
An automated native smoke mode verifies the full protected path:
PAPERBARK_SMOKE_TEST=1 \
dotnet run --project samples/minimal
Close-confirmation options, generated protocol interoperability, and Windows
policy lifecycle regressions run in dotnet test tests/Paperbark.Tests.
On macOS, run bash scripts/test-macos-close-confirmation.sh for native policy
and configuration parsing tests. With a macOS GUI session available, run
bash scripts/test-macos-close-lifecycle.sh to exercise actual AppKit menu,
primary close (including the bridge command), repeated requests, safe Escape/Enter,
nested modal cancellation/completion, confirmed quit, managed shutdown, disconnect,
and parent-loss paths. It creates isolated test windows and does not send system
keyboard input to other apps. Interactive acceptance still requires checking
Cmd+Q/Alt+F4 with WebView focus, primary close, cancel/Escape/Enter, repeated
requests, active modal/secondary windows, and forced shutdown while confirmation
is open on the target OS. Cross-building the Windows host does not verify Windows
keyboard routing or native focus behavior.
node --test tests/client/close.test.cjs checks the injected close facade.
dotnet run --project tests/Paperbark.CloseSmoke -c Release runs an isolated real
native/managed close fixture: responsive control commands during policy,
reentrant Busy, veto, exception, quarantine, reconciliation, actual closure, and
observer-before-managed-shutdown ordering. It intentionally logs a storage
exception without changing any external durable state. Both native CI jobs run
this fixture against their built host; it needs a GUI session and, on Windows,
WebView2. On Windows, pwsh -File scripts/test-windows-close-lifecycle.ps1 runs
the real WinForms/WebView2 confirmation, modal, timeout/recovery, disposal, and
transport-loss scenarios. The macOS lifecycle fixture also tests the native decision timeout and
late replies. System power-off is simulated with a process-local notification,
not by logging out or shutting down the machine. If AppKit termination arrives
without an observed system notification, Paperbark conservatively applies user
policy (including Dock Quit) instead of silently bypassing it.
It installs the cookie, loads protected content, changes the primary title,
executes the injected JavaScript API through WebView postMessage, routes the
request through ASP.NET/DI, opens an AppKit modal sheet over IPC, confirms its
protected backend navigation, closes it from dialog JavaScript, verifies the
returned result, and shuts down both processes.
The Blazor sample's circuit callback can be exercised specifically with:
PAPERBARK_SMOKE_TEST=1 \
PAPERBARK_SMOKE_DIALOG_ROUTE='/dialog?smoke=true' \
PAPERBARK_SMOKE_DIALOG_RESULT='blazor-circuit-smoke' \
dotnet run --project samples/blazor
Versioning and releases
.github/workflows/build-release.yml computes one version and uses it for
every platform build and the final package:
- Pull requests targeting
mainorrelease/X.Yproduce unique-pr.*packages and publish same-repository previews to GitHub Packages. - Pushes to
mainpublish-alpha.*packages for the next minor line. - Pushes to
release/X.Ypublish-beta.*packages for that line's next patch. - A manual workflow dispatch with
releaseenabled onrelease/X.Ypublishes the stable version, tags it asvX.Y.Z, and creates a GitHub Release with the package artifacts.
Local builds use 0.1.0-dev; set PAPERBARK_VERSION to override it. NuGet.org
publishing uses trusted publishing through the production GitHub environment.
Configure that environment with a NUGET_USERNAME secret and a NuGet.org
trusted-publishing policy scoped to this repository, workflow, and environment.
GitHub Pages remains independently built and deployed from main by
.github/workflows/pages.yml.
NuGet runtime assets
The intended package matrix is:
runtimes/osx-arm64/native/paperbark-host
runtimes/osx-x64/native/paperbark-host
runtimes/linux-arm64/native/paperbark-host
runtimes/linux-x64/native/paperbark-host
runtimes/win-arm64/native/paperbark-host.exe
runtimes/win-x64/native/paperbark-host.exe
The build scripts produce and embed both macOS and Windows architectures. The
Windows spike is a framework-dependent, single-file WinForms/WebView2 host and
therefore requires the .NET 10 Desktop Runtime and Evergreen WebView2 Runtime.
NativeHostLocator selects by operating system and process architecture, tries
the embedded content-addressed host first, then standard runtime asset
locations. PaperbarkOptions.NativeHostPath and
PAPERBARK_NATIVE_HOST_PATH remain available for development overrides. No
generated native executable is committed.
Testing direction
The HTTP capability model also supports browser automation without pretending a
native WebView is a new Playwright BrowserType. A future Paperbark.Testing
fixture can start the real ASP.NET application, create a browser context, and
install the same per-launch cookie with BrowserContext.AddCookiesAsync.
Chromium, Firefox, and Playwright WebKit can then test application behavior and
the server-side desktop bridge contract. A smaller native smoke suite should
continue to cover AppKit/WKWebView creation, script injection, IPC, and process
lifetime.
Platform follow-up
macOS: sign the generated app and its executables, package the host as a signed nested helper app rather than extracting a raw Mach-O, and add icons, entitlements, hardened runtime, notarization, and DMG/ZIP generation.
Windows: implement a Win32/WebView2 host, use an ACL-restricted named pipe,
install the capability through CoreWebView2CookieManager, inject the client at
document creation, and produce signed arm64/x64 runtime assets.
Linux: implement GTK/WebKitGTK, retain a permission-restricted Unix socket,
install the capability through WebKitCookieManager, inject the client with a
user content manager, and test Wayland/X11 lifecycle differences.
Cross-platform work also needs cache cleanup policy, crash recovery, protocol negotiation, richer multi-window identities, menus and dialogs, accessibility, native release builds for every future RID, and broader security hardening.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.2.0-alpha.16.1.c9da1a1 | 82 | 9/15/2026 |
| 0.2.0-alpha.12.1.d460a66 | 305 | 9/12/2026 |
| 0.2.0-alpha.10.1.115f194 | 74 | 9/12/2026 |
| 0.1.0-beta.8.1.35ae3c3 | 202 | 8/12/2026 |