← Documentation overviewDeveloper guide / DOMAIN

Meaning first.
Mechanics at the edge.

The actual domain vocabulary, service contracts and production adapters behind Lucent, including their behavior and current limits.

Build and VM status: this site's source links identify the documentation build. The VM can run a separate experimental release. Current readiness identifies accepted main, the deployed candidate, source-specific native results and remaining work.

01 / DEPENDENCIES

A port is a capability
the application asks for.

lucent-domain defines desktop data and outbound traits. lucent-usecases expresses decisions against those traits. lucent-services and lucent-auth implement external operations. The desktop executable chooses adapters in its composition root and supplies them to the client.

Dependency direction · arrows mean depends on
Client → use case → domain port ← production adapter
                       ↑                  ↓
                    test fake       OS / filesystem / IPC

Framework → Element / Layout / Paint / surface lifecycle
Domain    → values / contracts / policy inputs

The domain has no Wayland, D-Bus or GPU types and performs no OS operations. Its models use serde where persistence is needed, and authentication uses zeroizing storage. The framework does not depend on the desktop domain. Another shell can reuse the component and rendering crates with its own models and ports.

The domain’s Application is an installed-app record; the framework’s Application trait is the lifecycle contract for a UI executable. They are different types with different responsibilities.

Widget sizing is a concrete example of the boundary: WidgetSize validates the value, the pure use case constrains and snaps supplied bounds, the host owns transient gesture state, and the root schedules one settings writer. Native buffers and prepared pixels stay outside these domain values.

A port describes an operation, not its scheduling. Most methods are synchronous and may block. The client calls external work from effects or subscription workers; results return as typed messages. Send + Sync permits shared adapters—it does not make a method asynchronous.

02 / DATA WITH MEANING

The domain vocabulary.

ConcernTypesMeaning
Application identityAppId, Application, ApplicationLaunch, LaunchCommandAppId validates a desktop-file identity at construction and deserialization. Application carries searchable metadata and typed launch behavior: an executable argument vector, or desktop-service activation with an optional fallback. Services without Exec remain discoverable; the adapter owns native D-Bus naming and conservative fallback after uncertain outcomes. Its window-class matcher ignores missing identities and is shared by activation and dock presentation. Icons are names here, not textures.
Live wallpaperLiveWallpapers, LiveWallpaperSettings, WallpaperFrameRate, WallpaperFps, WallpaperPreviewOptions, MediaFramePure preferences and validated media data. WallpaperFrameRate selects source timing or a checked 1–60 FPS delivery cap; it never invents frames. WallpaperPreviewOptions checks a separate 1–1024 physical-pixel budget on each edge. The playback port returns decoded RGBA values; no FFmpeg, Vulkan, Wayland or filesystem type belongs in the domain. The client owns import/monitor/pause and transient preview policy; the root alone saves configuration.
Compositor stateWindow, Workspace, CompositorSnapshot, CompositorMonitor, WorkspaceRef, WorkspaceCommandWindows, workspace membership, focus, workspace counts and monitor-local active/special references. MonitorId and WorkspaceRef validate names/addresses; focus queries reject ambiguous identities. Workspace and window membership use validated numbered, named or special identities. WorkspaceCommand separates select from toggle; adapters encode native syntax. Window addresses remain opaque strings; the adapter interprets them.
Persistent preferencesDesktopSettings, WidgetLayout, MonitorId, Placement, WidgetSizeThe default and named monitor profiles share one pure WidgetLayout for positions, preferred sizes and visibility, pinned app IDs, dock preferences, notes, semantic palette selection and saved wallpaper palettes. Placement validates finite coordinates; WidgetSize validates finite positive dimensions at construction and deserialization. Output bounds, catalog minimums and grid tokens are supplied by the client. Query, edit mode, drag previews and animation progress are not saved.
Display configurationDisplayMode, DisplayScale, DisplayPosition, DisplayConfiguration, DisplayTransactionId, DisplaySnapshotChecked physical modes, thousandth scales and bounded signed logical positions are independent of native IPC. A whole-layout use case validates current unique outputs, mode capabilities and a remaining enabled screen. A transient transaction separates admission, verified preview, Keep, Revert and failure. The adapter owns clocks, process recovery, compositor authority and user-file persistence.
Monitor hostingMonitorId, WidgetLayoutMonitorId validates the persisted literal identity. Wayland OutputName and native objects stay in the SDK/backend; the desktop maps observations onto that identity and owns transient host generations. Widget use cases receive a borrowed layout. Notes, theme and service snapshots stay shared in the root, and only the root persistence coordinator saves profiles.
Observed servicesClockSnapshot, SystemSnapshot, MediaSnapshot, WeatherSnapshotValues the client displays without knowing their transport. System state includes CPU and memory. Audio and network have separate event-driven snapshots. Weather adds checked current conditions and three dated forecasts. Local behavior and reviewed visuals pass. Scoped native opening/focus and one production forecast are observed; broader form/provider acceptance remains separate.
Media playersMediaPlayerId, MediaTrackId, MediaTime, MediaRate, MediaSnapshot, MediaCommandTransient player and track handles identify observed lifetimes rather than bus paths or a global active player. Checked microseconds and finite nonzero rates support pure progress projection. Capabilities, ready/unavailable playback and optional validated artwork sources stay separate from prepared pixels. Commands capture the player and seeks also capture the exact track.
AudioAudioObjectId, AudioDeviceKey, AudioVolume, AudioSnapshot, AudioCommandTransient IDs identify observed devices and streams; a new connection allocates fresh IDs. Stable device keys can name preferences but do not authorize stale commands. Volume validates finite non-negative ratios and preserves observed amplification. Snapshots include defaults, device directions and application routes.
NetworkingNetworkObjectId, NetworkSsid, NetworkSnapshot, WifiCredentials, NetworkCommandTemporary handles identify devices, access points, saved profiles and active connections. SSIDs retain original octets rather than assuming UTF-8. Credentials consume a zeroizing Secret and cannot be cloned or debug-printed. Acknowledgement and observed scan/connection completion are separate.
BluetoothBluetoothObjectId, BluetoothAddress, BluetoothSnapshot, BluetoothCommandTransient handles identify current controllers/devices; address octets are a separate validated value. Observations distinguish discovery owned by our connection from global discovery, and pairing, trust, connection and service resolution. Records remain public data. Pairing conversations use transient attempt/request IDs, validated display codes and owned zeroizing PIN/passkey answers; the native desktop child is implemented and virtual-controller pairing/radio controls pass. Decline, Escape and unpaired Disconnect reach native observed termination on owned virtual controllers. Incoming/profile and physical acceptance remain pending.
Calendar & focusDate, FocusTimer, TimerPhaseDate::new validates a proleptic Gregorian date and derives its weekday (Monday zero). Relative-month arithmetic belongs to Date; the client limits navigation. The timer has Ready, Running, Paused and Finished states. Remaining time comes from a deadline, so delayed callbacks do not slow it down.
NotificationsNotification, NotificationAction, NotificationSnapshot, CloseReasonContent, action IDs, urgency/residency flags, timeout and active/history collections. An action is a protocol identifier, not an executable command.
Menus & sessionsMenuEntry, MenuOutcome, Secret, AuthPromptMenus preserve the original result, including disabled and parent/cancel behavior. Authentication is a conversation. Secret zeroizes its owned string and cannot be cloned or debug-printed.
Wallpaper selectionWallpaper, WallpaperAssetWallpaper describes the original source. WallpaperAsset records its staged immutable copy and provenance. These are data records; the storage adapter verifies ownership before commit or deletion. Decoded pixels stay in presentation.

These are the models implemented today. Network connections and Bluetooth devices now have typed observations and native adapters; the calendar remains date arithmetic and presentation. PowerPort now covers battery, power profiles, built-in brightness and session requests; tray items and native application menus now have a dedicated port and adapter. Calendar events remain unfinished. Public records are data; only types with checked constructors enforce value-object invariants.

Read desktop.rs, notifications.rs, menu.rs and authentication.rs.

03 / CONTRACT → ADAPTER → SYSTEM

What each port does.

Live source · application, compositor and settings ports
pub trait ApplicationPort: Send + Sync {
    fn discover(&self) -> Result<Vec<Application>>;
    /// Adapters own catalog updates and observation failure/recovery.
    fn watch(&self, emit: &mut dyn FnMut(Result<Vec<Application>>), stop: &dyn crate::StopSignal);
    fn watch_updates(
        &self,
        emit: &mut dyn FnMut(Result<ApplicationCatalogUpdate>),
        stop: &dyn crate::StopSignal,
    ) {
        self.watch(
            &mut |result| {
                emit(result.map(|applications| ApplicationCatalogUpdate {
                    applications,
                    artwork_changed: false,
                }))
            },
            stop,
        );
    }
    fn launch(&self, app: &Application) -> Result<()>;
    /// Optional fresh user intent; adapters without activation support may still launch.
    fn launch_with_context(
        &self,
        app: &Application,
        _context: crate::ApplicationLaunchContext,
    ) -> Result<()> {
        self.launch(app)
    }
    /// Open explicit targets in this application, rather than silently invoking
    /// a different default handler. Unsupported adapters return an error.
    fn open(
        &self,
        _app: &Application,
        _request: &crate::ApplicationOpenRequest,
        _context: crate::ApplicationLaunchContext,
    ) -> Result<()> {
        Err(DomainError::Unavailable(
            "Application opening is unavailable".into(),
        ))
    }
    /// Dispatch an explicitly selected declared action from the captured catalog
    /// descriptor. Adapters reject a descriptor that no longer matches their
    /// current source; action failure never invokes the main application entry.
    fn launch_action(&self, app: &Application, action: &ApplicationActionId) -> Result<()>;
    fn launch_action_with_context(
        &self,
        app: &Application,
        action: &ApplicationActionId,
        _context: crate::ApplicationLaunchContext,
    ) -> Result<()> {
        self.launch_action(app, action)
    }
}
pub trait CompositorPort: Send + Sync {
    fn snapshot(&self) -> Result<CompositorSnapshot>;
    /// Implementations validate command kind before native dispatch. Admission
    /// does not claim the observed workspace state has already changed.
    fn workspace(&self, command: WorkspaceCommand) -> Result<()>;
    fn focus_window(&self, address: &str) -> Result<()>;
    /// Adapters own event delivery, reconnects and any polling strategy.
    fn watch(&self, emit: &mut dyn FnMut(Result<CompositorSnapshot>), stop: &dyn crate::StopSignal);
}
pub trait SettingsPort: Send + Sync {
    fn load(&self) -> Result<DesktopSettings>;
    fn save(&self, settings: &DesktopSettings) -> Result<()>;
}
Port / operationsProduction adapterImplementation & client usage
ApplicationPort
discover, watch/watch_updates, launch/launch_with_context, launch_action/launch_action_with_context, open
XdgApplications ↗This merged domain port carries checked open targets and an optional opaque activation context; no Wayland handles enter the domain. The concrete XDG adapter parses current desktop entries, freshly revalidates captured descriptors and dispatches literal argv or typed Activate/ActivateAction/Open D-Bus calls. Definitive unsupported methods may use that application’s fallback; denial or uncertainty cannot launch a duplicate. Directory observations and artwork invalidation remain adapter IO. The client owns selection, input intent and effect scheduling. See docs/application-activation.md for delivery and native acceptance scope.
WallpaperPlaybackPort
library, prepare, play, preview
NativeVideoWallpapers ↗Discovers bounded local sources without decoding or selecting them. Prepare admits an owned import after real decoding and poster creation; play consumes that asset. Preview instead reads an unimported source and emits bounded MediaFrame values without writing assets or preferences. The adapter owns native file validation, decoding, timestamps and cooperative cancellation; the client owns preview demand, cache and worker retirement. WallpaperPlaybackPolicy/Context/PauseReason and a pure use case combine PowerPort, CompositorPort and SessionPort observations. The client freezes paused outputs and shares active background producers. Mailboxes and the generic SDK ImageStream keep renderer policy separate. See docs/live-wallpapers.md.
CompositorPort
snapshot, watch, workspace, focus_window
Hyprland ↗Reads JSON snapshots and subscribes to the event socket, reconnecting after failure. It subscribes before querying, preserves event fragments across read timeouts and shares an absolute snapshot deadline. The pure port also reports validated monitor focus and active/special workspace references; the client joins those observations with its native output hosts. See the independent compositor-service consumer and docs/compositor-focus.md.
DisplayPort
read, watch, apply, keep, revert
HyprlandDisplays ↗Fresh reads and watch captures observe active and inactive outputs in monotonically ordered revisions. Open/Reset request a current complete layout on the controls queue; closed, replaced or older replies cannot reset a later draft. Apply revalidates a complete captured baseline and starts the same executable in a private guardian mode before changing the display. An independent boot-time deadline and UI-pipe closure restore the captured layout. Keep rechecks the observed preview and atomically exchanges a marked tail in the captured user monitors.lua while preserving preceding rules and original-inode recovery. The native client reuses SDK fields, scroll, motion and generated tokens; seven real-input checks pass on one QEMU output, including Keep, explicit Revert, deadline and crash EOF rollback. Exact file preservation and the expected one-restart process transition are independently validated; physical/multiple-output acceptance remains open. See docs/native-displays.md and reports/measurements/lucent-display-settings.json.
SettingsPort
load, save
JsonSettings ↗Reads bounded regular desktop.json files without following the final symlink. Missing files use defaults; invalid files remain preserved with saving disabled. Shared domain notes admission applies on load and edit. Saves validate and bound JSON, then replace through a unique private sibling; the client coordinator orders saves.
ClockPort
now
LocalClock ↗Converts local civil time to ClockSnapshot. The desktop samples every second, but the minute clock does not repaint for every tick.
SystemPort
sample
LinuxSystem ↗Uses /proc/stat deltas and /proc/meminfo. A mutex protects the previous CPU sample. The desktop samples every five seconds. Changed observations invalidate the actual widget group and connected host instance; hosts hiding System and disconnected saved profiles do not request redraws. The domain and adapter do not decide which surface paints.
AudioPort
watch, control
PulseAudio ↗One native libpulse connection on the subscription worker observes devices, streams and defaults. Commands use a bounded queue and acknowledgement; the adapter reconnects and rejects known-stale IDs. PipeWire can provide the PulseAudio protocol. The desktop audio child owns bounded/coalesced UI intent and uses this contract; Native panel controls and silent virtual playback/capture routing are verified; physical device profiles and hardware remain open.
NetworkPort
watch, control, watch_credentials, answer_credentials
NetworkManager ↗One system-bus connection observes signals and owns a bounded command queue. Backend paths stay private; removal/reconnect retires transient handles. An independent network-service consumer exercises the library. The desktop root injects the same adapter into a native child. Its private credential form, snapshot-only view and effects use the port; compatibility entrypoints open this panel. Saved personal-Wi-Fi repair uses a separate one-shot credential observation/answer lane while activation awaits a native reply. Explicit SecretAgent registration pins the daemon owner and exact saved-profile context; credentials remain zeroizing and outside snapshots. Simulated WPA2 recovery is verified; physical, enterprise and vault acceptance remain separate.
BluetoothPort
watch, control, start_connection, cancel_connection, start_pairing, answer_pairing, cancel_pairing, set_incoming_pairing
BlueZ ↗One system-bus owner observes BlueZ and resolves bounded queued commands against current handles. Discovery ownership is connection-scoped; cancellation/timeouts explicitly close that connection. The independent bluetooth-service example consumes this contract. A bounded Agent1 conversation returns explicit prompts through the same watch contract while the owner continues routing answers and signals. Outgoing application registration differs from explicitly requested incoming/default authority; no automatic acceptance or trust. Native pairing protocol checks pass; the composition root now injects the adapter into a native child; native input, virtual-controller pairing/controls and external property observation pass; Decline completion and unpaired Disconnect now pass native virtual-controller checks; incoming/profile and physical validation remain pending.
PowerPort
watch, control
PowerServices ↗One system-bus owner aggregates independently available UPower battery, power profiles and logind capabilities. Built-in backlights retain private kernel identity and use the current owned Wayland session for native writes. Typed operation admission returns before authorization; signals and observed completion remain separate. The native child and independent consumer use the same port. VM and hardware acceptance are deferred.
TrayPort
watch, act, menu, invoke
StatusNotifier ↗Owns the session-bus watcher without replacing an existing owner. Current item handles, validated icon content and menu revisions stay separate from native paths. The Rust child uses SDK controls; an independent consumer uses the same port without Wayland. Owned SNI/DBusMenu protocol and native VM fixture checks pass, including gestures, overflow and owner retirement. Third-party tray applications and application-owned popup placement remain separate interoperability checks.
MediaPort
watch, start
Mpris ↗Native session-bus observation discovers running players and routes owner/property/seek signals. Short local admission returns an operation ID; exact native acknowledgement/error arrives through observation, separately from playback. Unique owners, transient track handles and bounded targeted commands prevent global-player retargeting. Progress is projected from a monotonic anchor; artwork preparation uses the separate presentation AssetPort. The independent media-service consumer uses the same contract without the desktop.
WeatherPort
legacy_preferences, forecast
OpenMeteo ↗The port accepts checked coordinates and a StopSignal. OpenMeteo implements bounded legacy reading, HTTP and provider-code/unit/date decoding. The child owns last-good presentation and request generations; the root owns saving. No automatic location detection. Native opening/focus and one production forecast are observed; form mutation and failure/retry acceptance remain separate.
ThemePort
apply(&ThemePalette)
LucentTheme ↗Receives validated semantic colors and invokes the token-export adapter. Omarchy consumes generated application configurations, with no reverse palette authority.
WallpaperCatalogPort
search, preview, download
OnlineWallpapers ↗Provider metadata, cached thumbnails and original download are separate from wallpaper activation. A Python HTTP adapter handles public endpoints, limits, redirects and provider parsing.
PaletteGenerationPort
seed(path)
ImagePalette ↗Decodes a bounded-size sample off the UI thread and selects a deterministic chromatic histogram seed. The design-system recipe turns that RGB value into semantic colors.
WallpaperPort
list, current, stage, commit, discard
LucentWallpaper / WallpaperStore ↗Discovery delegates to the local library; the store stages bounded immutable copies, reads the selected asset, atomically replaces its link, and discards failed candidates. The client must decode a staged asset before commit.
SessionPort
lock, watch_lock
LucentSession ↗Invokes the secure lock wrapper and observes typed Locked/Unlocked state for background-work policy. Hyprland supplies bounded monitor observations; missing fields or transport errors are unavailable, not an unlock. The client subscribes only while live wallpapers target an output. Authentication and secure unlock remain separate.
NotificationHistoryPort
load, save
JsonNotificationHistory ↗Reads a versioned bounded private archive and writes an exclusive temporary file before atomic replacement. Failed reads preserve files, private leases order instance handoff and delivery keeps live state separate from storage failure.
UriPort
open(ExternalUri, ApplicationLaunchContext)
XdgUri ↗Opens an explicitly selected supported URI through xdg-open in an independent application unit, with literal arguments and the explicit fresh launch context. The client requests an optional-target SDK activation token, revalidates the current notification version and URI at completion, and cancels retired queued work. Absent authority clears inherited tokens; historical links do not regain sender action authority.
NotificationPort
watch, dismiss, invoke, set_do_not_disturb, clear_history, retry_history
FreedesktopNotifications ↗Owns the freedesktop D-Bus notification service, feeds the pure inbox policy, publishes snapshots and emits action/close signals. Current-version targets reject stale actions. An injected history adapter runs on one ordered native storage worker with bounded latest demand; loading/saving acknowledgements are version checked. Mutation wakes and a 100 ms expiry/disconnect check drive delivery independently.

The application catalog guide follows the full path from injected ApplicationPort::watch through Linux directory events, typed catalog messages, icon preparation and launcher selection. The independent application-service example consumes the same port without a renderer. Saved pin/order policy lives in pure use cases; drag and visibility state live in the dock client.

Declared actions use ApplicationActionId, a checked case-sensitive value, and ordered ApplicationAction records. The pure launch_application_action use case requires one declared action; the production adapter freshly validates the entire captured application before native ActivateAction or that action’s own literal command fallback. The launcher owns menu/attempt revisions and task-local completion. Back retires the menu while accepted native work may finish. A reply acknowledges dispatch, not a new window. The actions guide traces each boundary and its bounds; the independent application-service consumer supports --action APP_ID ACTION_ID without a renderer. Native activation can still occupy the existing controls queue for up to five seconds.

Colors have values and invariants.

Rgb accepts exactly #rrggbb and exposes channels, luminance and contrast without renderer types. ThemePalette contains a stable ID, display name and ten semantic roles. Validation requires readable text/surface and text/accent pairs at 4.5:1 or better. DesktopSettings validates the selected set and its bounded palette library before persistence. Brightness is derived from the surface; it is not the palette’s identity.

Authentication has a separate contract.

AuthenticationPort::authenticate(identity, conversation) belongs to lock/login. PamLocker and Greetd translate backend prompts into AuthConversation. Success includes account checks and, for login, acceptance of the session-start request. The framework's secure session-lock protocol controls which surfaces protect the session; authentication alone does not make a surface secure.

The SessionScreen client owns field routing. Its computed identity-editing state supplies both the rendered username step and the keyboard handlers. A login attempt freezes that identity; response prompts use the client's zeroizing Secret storage. Clearing the field changes presentation state, not an authentication result. PAM, greetd and the authorization adapter remain responsible for accepting or rejecting the request.

Wallpaper search has its own worker.

The browser component owns query, provider, page, selected result and request generation. It declares a subscription keyed by that generation; the framework replaces the old stream when its identity changes. Search results and decoded previews return as typed messages. The component ignores responses from older generations, so a slow request cannot replace a newer provider selection. Provider work does not occupy the ordered settings/export worker. In-flight HTTP calls are bounded; cancellation prevents their result from being applied.

A selection is a transaction with a prepared image.

  1. The desktop reserves a selection ticket at the local choice or Download click. Network responses keep that ticket; receiving a response does not create new user intent.
  2. The Selection coordinator stages a private copy through WallpaperPort, decodes it through AssetPort, and optionally samples its palette through PaletteGenerationPort. All three adapters see the same snapshot.
  3. Preparation can be superseded. One active preparation and one latest pending choice bound rapid-click work. A stale or failed candidate is discarded without touching the selected link.
  4. After preparation succeeds, commit is accepted work. The adapter replaces the selected link; its completion carries the already decoded image back to the client. A later choice waits for this commit to finish before preparing, keeping visible and persisted state aligned.

Palette intent is separate: a newer explicit palette or extraction suppresses older automatic wallpaper colors. Extraction results also match the current asset path. Theme exports and the login copy synchronize after selection; they are separate operations, not one atomic filesystem transaction. Export failures remain visible. An ambiguous appearance-worker panic/admission failure stops the client so its supervisor can reload persisted state.

The network service also works without a desktop.

The independent network-service consumer constructs the adapter, starts one worker calling watch, and sends explicit commands from another thread. No component registration or Wayland surface is needed to use a domain port. The adapter connects to the system bus, pins the current unique service owner, converts properties to domain snapshots and refreshes after signals. Idle cancellation wakes do not query network state.

Live source · network port
pub trait NetworkPort: Send + Sync {
    /// One cancellable observer owns the native connection and reconnects. An
    /// error is unavailable state, never a fabricated disconnected snapshot.
    /// Do not call control synchronously from this callback: it runs on the owner.
    fn watch(&self, emit: &mut dyn FnMut(Result<NetworkSnapshot>), stop: &dyn StopSignal);
    /// Success acknowledges service acceptance, not connection/scan completion.
    /// Observations remain authoritative. A timeout can follow an accepted
    /// operation; cancellation cannot undo it. Forget deactivates then deletes,
    /// and is not atomic. Known-stale identities fail instead of retargeting.
    fn control(&self, command: NetworkCommand) -> Result<()>;
    /// Observe actual interactive requests independently of command admission.
    /// This must continue while a native activation is awaiting its reply. The
    /// snapshot contains no credentials; an empty list retires previous requests.
    fn watch_credentials(
        &self,
        emit: &mut dyn FnMut(Result<Vec<NetworkCredentialRequest>>),
        stop: &dyn StopSignal,
    ) {
        emit(Err(DomainError::Unavailable(
            "Network credential prompts are unavailable".into(),
        )));
        while !stop.cancelled() {
            stop.wait(std::time::Duration::from_secs(1));
        }
    }
    /// Consume a response to exactly one still-live request. None explicitly
    /// cancels it. Success acknowledges answer delivery, not association or save.
    /// Never retain/replay the credentials, even after an uncertain completion.
    fn answer_credentials(
        &self,
        _request: NetworkCredentialRequestId,
        _credentials: Option<WifiCredentials>,
    ) -> Result<()> {
        Err(DomainError::Unavailable(
            "Network credential responses are unavailable".into(),
        ))
    }
}

A successful command says the service accepted the request. The snapshot establishes scan completion, connecting, connected or authentication failure. A timeout retires the connection before reconnecting; already accepted service work cannot be undone by dropping a future. A device removal also invalidates dependent access-point/connection handles. Credentials never enter snapshots. Their borrowed D-Bus serialization still creates library/service-owned copies, so local zeroization is not an erasure guarantee for the whole system.

The network implementation guide explains the values, startup sequence, bounds, error handling and independent tests. The desktop composition root injects the adapter into its native Network child. That child borrows observations, owns its private zeroizing credential form and schedules typed commands; its view performs no network IO.

Media follows a captured player and track.

The composition root injects Mpris into the Media child. Its subscription delivers native observations; the view describes SDK elements and captures Pause(player) or SetPosition { player, track, position }. Pure command admission checks current identity/capability; the adapter resolves only that handle's current unique owner and revalidates native state. Neither layer substitutes a globally active player.

Live source · media port
pub trait MediaPort: Send + Sync {
    /// Observe current player lifetimes until cancelled. An empty successful
    /// snapshot means no players; an error means observation is unavailable.
    fn watch(&self, emit: &mut dyn FnMut(Result<MediaSnapshot>), stop: &dyn StopSignal);
    /// Admit one operation to a bounded local mailbox without waiting for native
    /// validation or a method reply. Busy/offline/full admission fails without
    /// dispatch. Watch the returned identity for method acknowledgement/error;
    /// admission never changes observed playback or guarantees target validity.
    /// Implementations revalidate player/track ownership before dispatch and
    /// retain terminal results until published. Watch failure makes a pending
    /// outcome uncertain; closing a client does not undo an admitted action.
    fn start(&self, command: MediaCommand) -> Result<MediaOperationId>;
}

Regular progress is calculated from a monotonic position sample, playback status and rate. A visible playing display can tick once per second without polling metadata. Artwork has a bounded source value in the domain, while the presentation AssetPort prepares pixels on its worker. The media implementation guide traces startup, selection, pending commands, progress, image lifetime and failure handling. Its independent consumer and private protocol fixtures remain distinct from real-player acceptance.

Two boundaries deliberately stay small.

StopSignal exposes cancellation and interruptible waiting to long-lived domain adapters. The client's WatchStop adapts framework cancellation to it. Domain code never receives the framework's message emitter.

AssetPort is a client port because it returns Arc<ImageData>. Its NativeAssets adapter loads bundled monochrome symbols, resolves fallback XDG icons and decodes wallpaper previews. Putting rendering types into Wallpaper or Application would cross the domain boundary. See the injected client bundle.

04 / DECISIONS YOU CAN TEST

Policy has no pixels.

search_applications ranks an in-memory list: exact name, prefix, substring, keyword/description and finally a name subsequence. It returns indices, preserving application identity. It performs no filesystem reads on a keystroke.

activate_application gives dock and launcher different behavior. A dock activation prefers an existing matching window. A launcher activation starts an application. If the compositor snapshot is unavailable, it can still launch; a failure to focus a matched window is returned to the client.

Live source · launch-or-focus policy
pub fn activate_application(
    apps: &dyn ApplicationPort,
    compositor: &dyn CompositorPort,
    app: &Application,
    prefer_running: bool,
) -> Result<()> {
    activate_application_with_context(apps, compositor, app, prefer_running, Default::default())
}
pub fn activate_application_with_context(
    apps: &dyn ApplicationPort,
    compositor: &dyn CompositorPort,
    app: &Application,
    prefer_running: bool,
    context: ApplicationLaunchContext,
) -> Result<()> {
    if prefer_running
        && let Ok(snapshot) = compositor.snapshot()
        && let Some(window) = snapshot
            .windows
            .iter()
            .find(|w| app.matches_window_class(&w.app_class))
    {
        return compositor.focus_window(&window.address);
    }
    apps.launch_with_context(app, context)
}

Placement::new guarantees finite coordinates. snap_placement validates a finite positive grid step and uses wider intermediates to round to a grid point; an unrepresentable result is rejected. constrain_placement returns a position bounded to the viewport without mutating preferences. move_widget uses it when committing a released widget to its stable ID's placement. The widget controller keeps interim previews outside the saved aggregate. The client supplies the spacing token, widget size and output bounds. The use case rejects non-finite or negative dimensions before changing settings. Screen-edge clamping can take precedence over exact grid alignment.

Selection owns menu filtering and navigation, skips disabled entries and preserves the original result value. NotificationInbox owns replacement IDs, bounded active/history collections, timeout decisions and DND. Its clock input comes from the adapter, so tests can advance time without sleeping. NotificationImage, NotificationPixels and NotificationImageSource are bounded content values with validated deserialization. Image bytes and archive retention are pure policy; AssetPort is a separate presentation boundary for prepared renderer data. Every receive has a separate revision from its replaceable wire ID; archives restore content as history rather than live sender authority. The native notification guide follows the port, worker, client and render path.

Bluetooth Decline demonstrates why an answer and an operation are different. The pure pairing_attempt_for_rejected_reply use case matches a current request to our live attempt for the same device. The client then delivers Reject and asks the port to cancel that captured attempt; the adapter checks that its identity is still current. An incoming request for another device cannot cancel our attempt, and display-only callbacks already use their port-level cancellation path. The client waits for an observed terminal phase rather than rewriting its snapshot after acknowledgement. Connected unpaired devices also have Disconnect beside Pair, without Trust. Policy/effect/layout tests and rebuilt native virtual-controller checks pass. The reference agent remains alive while inspection reports Cancelled after Decline/Escape or Failed after Disconnect; only then does test cleanup quit it.

Connect has its own operation identity. start_connection(device) returns a BluetoothConnectionId after admission; it does not block until authorization or establish a connected profile. The snapshot owns the attempt phase and separately reports device link/services state. cancel_connection(attempt) resolves only that live attempt and sends native Disconnect on the urgent lane. The adapter waits for Connect and cancellation replies in either order: denied cancellation cannot manufacture Cancelled, and a successful Connect remains Succeeded. validate_connection_target checks the current device, controller, pairing, power and blocked state without touching D-Bus.

The desktop keeps an admission placeholder until the matching snapshot arrives. Cancel before admission records intent and uses the returned ID when available; closing cannot reopen a late authorization prompt. Cancellation uses the reply queue while ordinary controls stay disabled. Views read observations and emit messages; they perform no OS calls and never rewrite the observed phase after an acknowledgement. Private-D-Bus and client behavior checks cover this path; real-daemon pending-profile and physical-device acceptance remain open.

05 / WHAT HAPPENS WHEN IT FAILS

Errors cross the same boundary.

DomainError distinguishes Unavailable, Invalid and Failed, each with context. Adapters translate external failures; the client decides how to present them. Audio explicitly keeps loading/ready/unavailable state and a separate command failure; unavailable audio clears stale volume. The client checks whether a failed request’s transient target still exists before offering Retry. If its identity changed, the client offers Dismiss and requires a new selection. Stable device keys do not silently retarget a failed command. This is not yet a universal loading/error state machine: media distinguishes initial loading, empty observation, global unavailability and per-player failure; the Weather child retains last-good data with separate error/freshness state (local behavior and scoped native opening/focus validated; failure/retry acceptance separate), while system sampling errors retain prior state, and action failures populate the desktop's error field.

Settings are schema version 1. Loading an unsupported or malformed file disables client saves, preserving the original. Missing files use defaults. Both loading and saving invoke validate_settings, which checks schema, palettes and launcher dimensions. Placement deserialization validates coordinates first, retaining the existing {"x":20,"y":100} storage shape; widget movement separately validates client-supplied dimensions. Invalid saves fail before replacing existing preferences. Settings errors are retained independently of wallpaper/action results, so an unrelated success cannot hide the reason saving is disabled. Temporary-file rename prevents readers from seeing partially written JSON; it is not a migration system or a promise of power-loss durability.

The shared command adapter supplies argument arrays, normally bounds execution to five seconds (45 for theme propagation, including subprocesses) and caps captured stdout at 8 MiB. Some features remain CLI-backed and polled. Omarchy menu actions are a separate integration: its Python adapter preserves trusted installed/user action semantics, including shell commands, after the native picker returns a value.

06 / EXTENDING THE BOUNDARY

Add behavior with evidence.

  1. Define the smallest domain value and port needed by the interaction. Keep transport and rendering types out.
  2. Write the decision as a use case when it is independent of UI state. Test it with a fake that records calls.
  3. Implement an adapter with explicit unavailable/error behavior. Wire it in the composition root.
  4. Map worker results to a client message, update state and invalidate the affected surfaces.
  5. Exercise the real integration separately. A fake-adapter test does not prove that D-Bus, a compositor socket or an installed command works.

Existing tests cover search ranking, dock focus versus launcher launch, timer catch-up, calendar arithmetic, widget bounds, notification replacement/expiry and menu navigation. See use-case tests and client tests with injected adapters.

Weather values and provider boundaries.

WeatherCoordinates and WeatherLocation enforce geographic and bounded-label invariants on construction and deserialization. WeatherTemperature checks finite Celsius values when the adapter constructs an observation. TemperatureUnit is an explicit persisted enum. WeatherCondition names meanings, not vendor codes. A snapshot carries three consecutive dates, checked high/low values and observation/fetch times.

WeatherPort is the contract. OpenMeteo is the adapter implementing it with bounded legacy reading and curl/JSON transport. A fake adapter can return known observations without Linux networking or a renderer. The production composition root chooses the implementation; Rust source modules are compiled into the executable rather than loaded as shell plugins.

The Weather child owns the draft, generations, loading/error/stale presentation and clipboard lifetime. Apply returns a save intent; the root changes DesktopSettings.weather through its existing persistence coordinator. Explicit disabled settings outrank legacy import. Views emit SDK elements and perform no I/O. Read the complete startup, request and frame path for local and scoped native evidence, one production forecast observation and pending form/failure/pacing acceptance and the current resource limits.