Last time, Fadrio's UI was a beautifully honest empty window. All the interesting work was underneath it: PipeWire events, application identity, multi-stream grouping, and commands that target an application instead of one transient audio node.
Now the first live application row is on screen. It has a name, a volume slider, a mute button, and a useful sense of self-preservation when the audio backend disappears. The screenshot above is the actual Avalonia window connected to a silent, isolated PipeWire fixture—not a mockup and not a screenshot of somebody's private desktop activity.
One row, several moving parts
The visible control is deliberately simple. An application can own several playback streams, and the mixer coordinator groups those streams under one stable application identity. The row presents the group's effective volume. If member streams disagree, it says mixed instead of quietly inventing a single source of truth.
The data path now looks like this:
| Boundary | Responsibility |
|---|---|
| Native PipeWire bridge | Copy callback data immediately and publish managed audio events |
| Mixer coordinator | Serialize changes and publish immutable application snapshots |
| Avalonia dispatcher | Move only presentation updates onto the UI thread |
| Row view model | Keep a stable row per application ID and expose volume/mute commands |
The slider and mute button use the same application-wide command path as the CLI. Neither a PipeWire node ID nor a process ID appears in the normal row. A browser with multiple streams should still look like a browser, not an anatomy lesson.
This thread boundary matters. A native callback is not allowed to wander into Avalonia and rearrange controls. The UI receives a finished snapshot. When PipeWire disconnects, controls disable; when it returns, the reconnecting backend rebuilds the snapshot. The audio service is permitted to have a dramatic exit. The interface does not have to join it.
Preferences that survive the next launch
The other big piece in this update is SQLite-backed application identity storage. Fadrio now creates its database in the per-user XDG data location, with versioned, checksummed, transactional migrations. It can remember a custom application name or icon choice across restarts and resolver refreshes while keeping the canonical ID unchanged.
That database is intentionally boring about private data. Stable desktop, Flatpak, Snap, and Steam identifiers can be recorded with allowlisted evidence. Path-derived fallback identities use opaque keys, and resolved process paths, media titles, and raw runtime IDs do not become a permanent diary of what you ran.
PID is diagnostic data, not identity. Persisting it would be like naming a ship after the parking space it briefly occupied.
What was actually checked
The local managed/native suite and isolated PipeWire integration passed. The UI's view-model tests cover snapshot refresh, row reuse, ordering, command targeting, mixed volumes, and disconnect behavior. I also launched the real window against a controlled silent stream and captured the screenshot above.
There are two limits worth saying plainly. I have not independently qualified an on-screen slider drag or screen-reader output yet. GitHub Actions runs were canceled because Actions minutes are exhausted, so this is not a claim of remote CI qualification. There is no new release tag on the back of canceled checks.
The next turn of the fader
This is the first functional slice, not the final visual mixer. Output-device controls, resolved icons, stable interaction behavior, tray handling, localization, and scaling still need work. The current screenshot has all the aesthetic confidence of a construction site wearing a clean shirt—and that's fine. It now gives us a real interaction surface to improve.
The implementation is in the identity persistence PR and the live mixer UI PR. If you use a Linux mixer today, what is the most annoying application-grouping or volume-control behavior you wish would disappear?
