Eleven resolved decision tickets plus two research notes, produced with /wayfinder before any code was written. They live next to the code they explain rather than in the issue tracker: they are reasoning documents, not open work, and in a tracker they would drift from the code with no diff and no review. The value is in the rejected alternatives. Why a counter under a row lock and not BIGSERIAL, why an event log and not a column on messages, why resync is a 200 with a discriminated body and not a 4xx, why the fan-out trait was deleted again, why voice is peer-to-peer with a hard six-person cap and no speculative SFU seam, and why Linux needs a native media stack while the other three platforms do not. Two entries record where the spec was wrong and got corrected: created_at was never a safe sort key, and an unread count cannot be derived without fetching because seq counts events rather than messages. Open work is tracked as issues, not here.
9.1 KiB
Voice auf Linux
Type: grilling Status: resolved Blocked by: — Parent: ../map.md
Question
Was ist die Zusage an Linux-Nutzer bezüglich Sprachanrufen — und wenn Voice dort laufen soll, über welchen Medien-Stack?
Aufgeworfen von Recherche: WebRTC im Tauri-WebView (Mobile). Die erwartete schlechte Nachricht kam nicht von Mobile, sondern von Linux.
Befundlage (belegt, siehe §5 der Recherche)
WebRTC ist in Tauris Linux-WebView (WebKitGTK 4.1) an drei unabhängigen Gates blockiert:
- Compile-Time.
ENABLE_WEB_RTCfolgtENABLE_EXPERIMENTAL_FEATURES, das aufOFFsteht. Debian/Ubuntu und Alpine setzen es belegbar nicht →RTCPeerConnectionexistiert im JS-Global gar nicht. Arch/Fedora unverifiziert (Experiment E5 in der Recherche). - Runtime-Setting.
WebKitSettings:enable-webrtcist DefaultFALSEund wird von wry nie gesetzt. - Permission.
permission-requestist Default Deny; wry 0.55.1 verbindet das Signal überhaupt nicht.
Gate 2 und 3 wären aus dem App-Code über WebviewWindow::with_webview lösbar (bzw. ab
Tauri 2.12 sauber über on_permission_request). Gate 1 hängt am Distro-Paket des
Nutzers und ist von uns nicht lösbar.
Das trifft ausgerechnet die Gruppe, die für dieses Projekt zentral ist: Selbsthoster sind linux-lastig.
Optionen, alle unattraktiv
- Voice auf Linux als nicht unterstützt deklarieren. Chat läuft voll, Voice nicht. Ehrlich und billig — und wahrscheinlich der Weg, der die Simplizitätsdoktrin am wenigsten verletzt. Aber schmerzhaft für die Kernzielgruppe.
- Nativer Medien-Stack nur auf Linux.
livekit/rust-sdks(libwebrtc-Crate, echtes libwebrtc mit NetEQ/AEC/Opus, laut README inkl. Linux) im Tauri-Rust-Layer. Bricht die Architektur-Einheitlichkeit: Voice läge auf drei Plattformen im WebView und auf einer in Rust. Zusätzlich ein großer vorkompilierter nativer Blob pro Plattform. (webrtc-rsist hierfür kein Ersatz — es ist ein reiner Protokoll-Stack ohne Geräte-Capture, ohne Codecs, ohne Jitter-Buffer, ohne Echo-Cancellation.) - Nativer Stack auf allen Plattformen. Einheitlich, aber verwirft den kostenlosen WebView-Pfad auf drei von vier Zielen und zieht den nativen Blob überallhin.
- Eigenes WebKitGTK bündeln. Widerspricht der Simplizitätsdoktrin frontal.
- Auf Upstream warten. Löst Gate 2 und 3, nie Gate 1. Also keine Lösung.
Zu entscheiden
- Welche Option — und wie wird die Einschränkung dem Nutzer gegenüber sichtbar gemacht,
statt ihn in einen Timeout laufen zu lassen? (Feature-Detection
typeof RTCPeerConnectionist trivial und sollte in jedem Fall passieren.) - Falls (2) oder (3): wo verläuft dann die Rust↔WebView-Naht? Bei nativem Audio ist der WebView nicht mehr der Medien-Endpunkt — Capture und Wiedergabe liegen in Rust, und die UI bekommt nur heruntergerechnete Metering-Werte über IPC, niemals rohes PCM. Zurück-Injizieren von PCM in den WebView geht in Chromium, aber nicht in WebKitGTK. Rückkopplung auf Wo lebt die WS-Verbindung im Client. Beantwortet (14.08.2026): Linux ist eine vollwertige Client-Plattform. Damit ist der billige Ausweg verbaut — Option 1 bedeutet, auf einer erklärten Zielplattform ein Kernfeature nicht anzubieten. Sie bleibt formal wählbar, muss aber als bewusster Produktverzicht begründet werden, nicht als technische Notwendigkeit.
Realistisch bleiben damit (2) nativer Stack nur auf Linux und (3) nativer Stack überall. Die eigentliche Frage dieses Tickets ist die Abwägung zwischen diesen beiden: Einheitlichkeit gegen Kostenminimierung.
Kopplung
Wirkt auf Voice-Topologie zurück: fällt hier ein nativer Pfad, ändern sich dort die Optionen. Die beiden Tickets können in beliebiger Reihenfolge laufen, aber das zweite muss das Ergebnis des ersten kennen.
Answer
Entschieden am 14.08.2026.
0. Zusätzlich geprüft: Flatpak löst Gate 1 nicht
Die Recherche hatte Gate 1 als „hängt am Distro-Paket" eingestuft. Das gilt aber nur, solange der Nutzer das Distro-Paket benutzt — ein Flatpak bringt WebKitGTK aus seinem Runtime mit. Geprüft, Ergebnis negativ:
- Das GNOME-Runtime baut WebKitGTK ohne den WebRTC-Flag; die Antwort im Flathub-Forum auf die Frage nach dem Patchen lautete sinngemäß „macht ein Issue bei gnome-build-meta auf" (Flathub Discourse).
- Das einzige dokumentierte funktionierende Rezept verlangt, WebKitGTK selbst zu
kompilieren (
-DENABLE_MEDIA_STREAM=ON -DENABLE_WEB_RTC=ON) und zusätzlich wry zu patchen — erprobt auf NixOS unter X11, mit GBM-Fehlern unter Wayland und nicht aufgelisteten v4l2loopback-Geräten (Tauri Discussion #8426).
Für ein Entwicklungssystem machbar, für Endnutzer nicht. Gate 1 hat heute keine nutzerseitige Lösung.
1. WebView auf drei Plattformen, nativer Stack nur auf Linux
Windows, Android und iOS nutzen den kostenlosen, funktionierenden WebView-Pfad. Linux bekommt
livekit/rust-sdks (libwebrtc-Crate) im Rust-Layer — echtes libwebrtc mit NetEQ, AEC/AGC/NS
und Opus, laut README inklusive Linux.
webrtc-rs ist kein Ersatz: reiner Protokoll-Stack ohne Geräte-Capture, ohne Codecs, ohne
Jitter-Buffer, ohne Echo-Cancellation.
Die Alternative „nativ überall" war knapp und hat ein starkes Argument — ein Pfad statt zwei, eine Berechtigungslogik, eine Testmatrix, und die wry-Eigenheit (Mikrofonrechte werden auf iOS bedingungslos an jeden Origin vergeben) wäre komplett aus dem Spiel. Sie verliert, weil sie einen auf drei Plattformen funktionierenden Gratis-Pfad wegwirft. Der akzeptierte Preis sind zwei Medienpfade auf Dauer, mit zwei Berechtigungsmodellen und zwei Fehlerbildern.
Nützliche Nebenwirkung: weil Linux den nativen Stack unbedingt nutzt und nicht nur dort, wo die WebView-Variante versagt, spielt es keine Rolle mehr, ob eine Distribution WebKitGTK mit WebRTC gebaut hat. Experiment E5 der Recherche (Arch/Fedora prüfen) entfällt damit. Und weil es hier nur um Audio geht, ist der GBM-/Wayland-Fehler aus #8426 gegenstandslos — er betrifft die Videoausgabe des WebViews.
2. Die Naht ist eine explizite Verzweigung im Frontend, ganz oben
Rust meldet dem Frontend eine Fähigkeit — sinngemäß mediaOwner: "webview" | "native" | "none"
— und das Voice-Modul verzweigt einmal, an seiner Spitze. Darunter zwei klar getrennte
Implementierungen: RTCPeerConnection im WebView, oder Tauri-Commands in den nativen Stack.
Das ist ehrlich gegenüber der Realität, dass sich die Fälle nicht nur mechanisch
unterscheiden, sondern auch in der Oberfläche: auf Linux gibt es kein <audio>-Element,
weil PCM sich in WebKitGTK nicht zurückinjizieren lässt (in Chromium ginge es). Der native
Pfad besitzt dort die Wiedergabe vollständig, und Pegelanzeigen kommen als
heruntergerechnete Werte über IPC — niemals rohes PCM.
Verworfen: ein MediaEngine-Trait in Rust. Es wäre die sauberere Fassade, verlangte aber eine
Steuerungsumkehr — die WebView-Implementierung müsste ihre eigentliche Arbeit per IPC nach
oben ans Frontend delegieren. Ebenfalls verworfen: eine Naht nur auf Signaling-Ebene, weil die
Duplizierung dann bis in die Oberfläche reicht und Verhaltensunterschiede versehentlich statt
absichtlich entstehen.
3. Gestaffelt: Voice erscheint zuerst auf den WebView-Plattformen
Linux zeigt Voice solange sauber als „noch nicht verfügbar" an — über die gemeldete Fähigkeit
none, nicht über eine WebView-Feature-Erkennung, weil die auf Linux dauerhaft negativ
bliebe und nichts über den nativen Pfad aussagt. Chat läuft dort voll.
Das Argument ist Risikoreihenfolge: der WebView-Pfad validiert das Signaling-Protokoll billig auf drei Plattformen, und der native Stack steckt danach in ein bereits bewährtes Protokoll. Den schweren, unerprobten Medien-Stack gegen ein gleichzeitig unerprobtes Protokoll zu bauen, verdoppelt die Unbekannten in einem Schritt — und bei einem Fehler wüsste niemand, welche Seite schuld ist.
Bewusst in Kauf genommen: die Zielgruppe ist linuxlastig, und „Voice, aber nicht bei dir" ist keine gute erste Begegnung. Wer es einmal als „geht auf Linux nicht" abgespeichert hat, prüft später nicht nach. Das ist der Preis dafür, das Feature überhaupt in Nutzerhände zu bringen, bevor Monate in den nativen Stack fließen — und er ist erträglich, weil die Ansage ehrlich und sichtbar ist statt stillschweigend.
Folgen für andere Tickets
- Voice-Topologie: der native Stack liefert auf Linux echtes libwebrtc, Mesh funktioniert dort also genauso. Die Topologie-Entscheidung wird durch dieses Ticket nicht eingeschränkt.
- Voice-Signaling-Protokoll: der signierte
DTLS-Fingerprint muss auf beiden Pfaden erzeugbar sein — im WebView über
RTCPeerConnection.generateCertificate(), nativ über die Zertifikats-API von libwebrtc. Die Naht für das Pinning darf also nicht an einer WebView-API hängen.