Three home-screen issues from the first Tauri Android APK:
1. Alias was empty (no seed-derived name).
Port the adjective+noun word lists from the old Kotlin SettingsRepository
into a `derive_alias()` helper that maps the first 4 bytes of the seed to
indices in those lists. Same seed → same alias forever, different seeds →
effectively random aliases — so reinstalls keep the user's identity AND
the friendly name they're used to.
2. Build identity was invisible — couldn't tell which APK was actually
installed (this caused us a lot of grief on the Kotlin app).
build.rs now captures `git rev-parse --short HEAD` and emits it as
`WZP_GIT_HASH`, exposed via a new `get_app_info` command. The frontend
stamps `build <hash> • <alias>` under the fingerprint on the home screen.
3. Register on relay failed with `Permission denied (os error 13)`.
Root cause: I hardcoded `/data/data/com.wzp.phone/files/.wzp` as the
identity dir, but the Tauri Android package id is `com.wzp.desktop` —
so the app was trying to write into another app's data directory and
getting EACCES at the filesystem layer. Fix: resolve the data dir from
Tauri's `path().app_data_dir()` API in the `setup()` callback and stash
it in a `OnceLock<PathBuf>`. Works on Android, macOS, Linux, Windows
without any cfg gymnastics.
Also: `get_app_info` returns the resolved `data_dir` so we can debug
storage issues from the UI (it's set as the build-hash element's title).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tauri 2.x Mobile links the app as a cdylib loaded from a Java Activity, so
all of the Builder/command code has to live in a library crate. Move the
existing logic verbatim into src/lib.rs::run() and reduce src/main.rs to a
two-line desktop entry point that calls into it.
Cargo.toml gets a [lib] section (crate-types: staticlib + cdylib + rlib,
named wzp_desktop_lib) and the wzp-client dependency — which pulls CPAL +
VoiceProcessingIO — is moved behind cfg(not(target_os = "android")) so the
Android cdylib doesn't need an audio backend yet. Engine-backed Tauri
commands (connect/disconnect/toggle_mic/toggle_speaker/get_status) get
Android stubs that return clear "not yet wired" errors. The signaling
commands (register_signal/place_call/answer_call/get_signal_status/
ping_relay/get_identity) are platform-independent and unchanged.
Also: get_identity / register_signal now auto-create the seed if missing
instead of erroring with "connect to a room first", and the identity dir
resolves to /data/data/com.wzp.phone/files/.wzp on Android (proper
app-internal storage) vs \$HOME/.wzp on desktop.
Side note: src/main.rs was previously untracked — desktop builds were
working only because it existed in the local worktree. This commit fixes
that too.
Step 1 of the Android rewrite plan (tauri-mobile scaffold). No audio yet.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Desktop now shows codec badges like Android:
- Green TX badge: e.g. "Opus64k"
- Blue RX badge: e.g. "Opus24k"
Displayed in the stats line below the call controls.
Engine tracks tx_codec (set on encoder init) and rx_codec (updated
from incoming packet headers). Passed through EngineStatus → CallStatus
→ frontend.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
RoomParticipant now has optional relay_label field. Desktop client
groups participants by relay: "This Relay" (green dot) for local,
peer label (blue dot) for federated. Shows all relays in the chain
including intermediate ones.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds three new codec IDs (Opus32k=6, Opus48k=7, Opus64k=8) and
corresponding STUDIO_32K, STUDIO_48K, STUDIO_64K quality profiles.
All use 20ms frames with minimal FEC (10%) for maximum quality on
good networks.
Updated across: wire protocol (codec_id.rs), encoder/decoder
(opus_enc/dec.rs), adaptive codec switch (call.rs), CLI
(--profile studio-64k), desktop engine + UI slider (8 quality
levels from Studio 64k green to Codec2 1.2k red).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a Quality dropdown (Auto / Opus 24k / Opus 6k / Codec2 3.2k /
Codec2 1.2k) to both the connect screen and settings panel. The
selected profile is passed through to the engine which configures
the encoder and decoder accordingly.
The desktop engine recv path now auto-switches the decoder codec
when incoming packets use a different codec than expected, enabling
cross-codec interop between clients on different quality settings.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Identicon generator:
- Deterministic 5x5 symmetric pattern from fingerprint hash
- HSL-derived colors, rendered as inline SVG
- Click any identicon to copy its fingerprint to clipboard
- Used for participants, user identity, and relay servers
Server identity (TOFU — Trust On First Use):
- Ping returns server fingerprint (QUIC peer certificate hash)
- First contact: auto-saved as known fingerprint
- Subsequent pings: compared against known fingerprint
- Lock icons: locked (verified), unlocked (new), warning (changed), red (offline)
- Fingerprint mismatch shows confirmation dialog before connecting
UI updates:
- Participants show identicons instead of letter avatars
- User identity shows identicon + fingerprint on connect screen
- Manage Relays shows identicon per server with lock status
- Relay button shows lock icon instead of colored dot
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New ping_relay Tauri command: QUIC connect with 3s timeout, returns RTT ms
- Relay status shown next to input field: "42ms" (green) or "offline" (red)
- Auto-pings on app startup and debounced on relay input change
- Fix SyncWrapper dead_code warning with #[allow(dead_code)]
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
#7 Fingerprint shown before connecting — new get_identity command reads
~/.wzp/identity at startup (generates if missing). Click to copy.
#8 Recent rooms store (relay, room) pairs — clicking a chip fills both
fields. Settings panel shows relay alongside room name. Migrates
old string[] format automatically.
#9 Auto-reconnect on unexpected disconnect — exponential backoff
(1s, 2s, 4s... max 10s), up to 5 attempts. Yellow blinking dot
shows reconnecting state. Stops if user clicks hangup.
#10 Audio handle cleanup — CPAL handles stored in SyncWrapper (no more
mem::forget), dropped properly on CallEngine::stop().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The VPIO/CPAL audio handles were dropped at the end of start(),
killing the audio unit immediately. Audio I/O stopped working
after the first frame.
- Store audio handle in CallEngine via SyncWrapper
- Drop MutexGuard before returning from status() (Send future)
- Audio streams now live for the entire call duration
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Change status() from blocking_lock to async lock().await —
fixes "Cannot block the current thread from within a runtime" panic
that froze the call timer and broke audio
- Click fingerprint to copy to clipboard (both connect and settings screens)
- Show "Copied!" feedback on click
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
send_datagram() returns Err(Blocked) when the QUIC congestion window
is full. This is transient — the window reopens once ACKs arrive.
Previously, all send paths treated this as fatal (break/return),
which killed the send task and cascaded via tokio::select! to kill
the entire call.
Now: log warning, drop the packet, continue. Brief audio glitch
(20-100ms) instead of complete call death. FEC on the receiver
side recovers most dropped packets.
Fixed in:
- CLI run_live send task (continue + error counter)
- CLI run_file_mode send paths (2 locations)
- Desktop engine send task
Also hardened recv tasks: transient errors (non-closed/reset)
are survived instead of causing exit.
Matches the fix applied to Android client (engine.rs).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Audio level meter with log-scale RMS visualization
- Call duration timer
- VPIO (OS AEC) wired through to engine with fallback to CPAL
- "You" badge on own participant entry
- Recent rooms list (click to reuse)
- Enter key to connect from form fields
- Improved dark theme with pulse animation on status dot
- Settings persistence via localStorage (relay, room, alias, AEC, recent rooms)
- Fingerprint display on connect screen
- Keyboard shortcuts skip input fields
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New desktop/ directory with Tauri v2 + Vite + TypeScript
- Rust backend: CallEngine wrapping wzp-client audio + transport
- Web frontend: connect screen, in-call screen with participants,
mic/speaker mute, keyboard shortcuts (m/s/q)
- Dark theme UI, settings persistence via localStorage
- Platform-aware --os-aec: warns on Windows/Linux (not yet implemented)
- Workspace updated to include desktop/src-tauri
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>