Commercial desktop features live in rivet/system and native platform adapters, not in RVT1. This boundary keeps the embedded transport small and makes UI-thread and lifecycle ownership explicit.
rivet/system exports single-instance acquisition and activation routing, system notifications, tray/menu-bar configuration, login/autostart control, secure binary secret storage, atomic JSON settings, structured logging, and provider-neutral crash-reporting hooks.
Generated native hosts call the first-party Swift/C++ system libraries directly, on the native UI or lifecycle thread that owns the operation. They do not install Racket procedures into the embedded runtime and system services do not extend RVT1. Racket-side providers and headless tests can parameterize current-system-adapter; without such a provider, the Racket convenience procedures fail clearly instead of silently using insecure storage or a fake notification.
platform/windows/system uses first-party Win32 surfaces:
- named mutexes for process uniqueness;
- command-line activation for URL schemes and file associations registered by MSI;
Shell_NotifyIconWfor tray presence and notification balloons;- the current user's
Runregistry value for opt-in autostart; - Windows Credential Manager (
CredWriteW/CredReadW) for secrets; SetUnhandledExceptionFilterandRegisterApplicationRestartas low-level crash/restart hooks.
The host owns the window handle and receives tray callbacks on its WinUI thread. Applications can replace the built-in crash callback with Crashpad, Sentry, a private endpoint, or no uploader; Rivet does not bind to any provider.
RivetSystem uses AppKit and Apple system frameworks:
- an OS file lock automatically released on process death;
- SwiftUI
onOpenURLplus packagedCFBundleURLTypes/CFBundleDocumentTypesmetadata; UNUserNotificationCenter;NSStatusItemfor menu-bar UI;SMAppService.mainAppfor login-item registration;- Security.framework Keychain generic-password items;
- an activation router that stays on the main actor.
macOS asks the user for notification and login-item consent as required by the OS. Code must not treat denial as a crash.
UserNotifications is available only to a packaged .app with an application identifier. RivetNotifications.isAvailable reports whether the running process has that identity. Development executables such as .rivet/stage/RivetHost therefore return false from requestAuthorization() and show(...) throws the catchable RivetSystemError.notificationsUnavailable, instead of entering UNUserNotificationCenter and triggering an Objective-C exception.
platform/linux/system uses first-party Linux desktop surfaces, selected for the same independence rules as the other platforms:
- an abstract-namespace Unix socket for the single-instance lease; secondaries forward their activation arguments to the primary over that socket;
ActivationArguments()from/proc/self/cmdlinefor URL-scheme and file-association payloads registered by installers;org.freedesktop.Notificationsover the session bus (GDBus), with stable notification ids for tagged replacements;TrayIcon— org.kde.StatusNotifierItem plus a com.canonical.dbusmenu menu, through the session bus (GDBus). This is an explicit opt-in, not an adapter default: desktop hosting is compositor-dependent (GNOME hosts it only through the AppIndicator extension, KDE natively), so the application checksTrayIcon::available()(or thetraycapability) and decides. The icon, tooltip, and flat menu (labelled items, separators, enabled state) update in place; the watcher is re-registered whenever it reappears, so GNOME Shell restarts and extension reloads do not strand a running process without its icon. Methods and menu callbacks run on the thread-default main context the icon was constructed on — for GTK hosts, the main-loop thread;- XDG autostart entries under
$XDG_CONFIG_HOME/autostart; - the Secret Service provider (GNOME Keyring, KWallet bridge) through libsecret for binary secrets; the capability is compiled in when libsecret is present and reported at runtime only when a provider is reachable;
sigactionfatal-signal hooks that append a preformatted note (signal number plus restart arguments) to a file under the XDG state directory.
rivet::system::Capabilities() reports what the running session actually provides; a missing session bus, Secret Service provider, or libsecret build fails clearly at call time instead of silently degrading.
Install rivet::system::InstallShutdownHook only after the embedded backend's
start() has completed its Hello handshake. racket_boot establishes Racket
CS signal handling during startup; installing the application hook earlier
would let that initialization replace it, turning SIGTERM into a backend
break while the GTK main loop remains alive. Generated hosts and the reference
Taskboard enforce this ordering and the deb lifecycle gate sends a real
SIGTERM to the installed application.
The Linux integration binary self-checks the adapter (RivetIntegration --system): lease acquisition and activation forwarding, autostart entries, and the crash hook always run; notification, tray, and secure-storage checks skip themselves when the session lacks those services.
make-settings-store reads a JSON object and writes updates through an atomic replacement under a semaphore. The application chooses the path, normally its platform Application Support/AppData directory (on Linux, a file under the XDG state or config directories). Secrets do not belong in settings; use secure storage.
rivet-log emits structured records to current-rivet-log-sink. call-with-crash-reporting records an escaping Racket exception and invokes current-rivet-crash-reporter before re-raising it. Native fatal hooks are deliberately minimal: crash handlers must avoid allocations and network access, then let a helper submit the report on the next launch.