1. Voice Bar 'Leave voice' button removed entirely. TeamSpeak users
are always in some channel; Discord/Mumble-style leave is the
wrong model. To stop being heard / hearing, mute mic / speaker.
To physically move, tap a different channel. The voiceLeave
bridge call + _onLeaveVoice stay as dead code for now (marked
unused) so existing tests/integrations don't break.
2. voice_join now confirms the move actually applied server-side
by polling the snapshot for up to 1.5 s and matching our own
client's channel against the requested one. If the server
rejected the move (no permission, wrong password, channel
full), voice_join rolls the selector back to in_channel=false
and returns Err so the UI surfaces the failure instead of
showing a fake 'joined' state.
3. ServerSnapshot + BridgeSnapshot gain own_client_id so the UI
can identify our row without name-matching. find_own_in reads
it directly.
4. set_self_muted now also clamps the TransmitModeSelector's
hard_mute when input is muted server-side. Without this, the
Opus encoder kept producing frames after setInputMuted(true),
tsclientlib refused each one with 'Sending audio while muted',
and the log grew to 200 MB on the Korean host.
5. tsclientlib WARN spam suppressed via tracing filter
(tsclientlib=error). Belt-and-braces on top of fix 4.
6. Log file is now rotated at every launch (not just when >4 MiB).
Two generations kept: chanora.log.1 (previous) and
chanora.log.2 (the one before). The bug that produced 200 MB
files was a chatty subsystem flooding a single session; the
per-launch rotate keeps disk use bounded by what one session
can produce in its lifetime.
Bonus Windows fix (separate from the six but found in the same
log): the Raw Input + Hook backends now signal readiness BEFORE
blocking on GetMessageW. Previously init_tx.send was called after
the loop returned (i.e. on WM_QUIT, which never happens during
arming), so the main thread's 2 s readiness probe always timed
out and the backend reported L0Focused even when registration
succeeded. Both run_raw_input_loop and run_hook_loop now take an
init_tx parameter and call report!(true) right after a successful
registration, and report!(false) on every early-fail return.
cargo check --workspace: clean.
cargo test --workspace --lib: 80 passed / 0 failed / 1 ignored.
flutter analyze: clean (6 pre-existing Radio.groupValue infos).
First Internal Alpha build per DEC-001. Closes the milestone of
'Flutter UI calls Rust via the typed bridge, Rust connects to a
TeamSpeak-compatible server through tsclientlib, returns a typed
snapshot, and disconnects cleanly.' Audio remains Beta scope.
Promotions from PoC:
poc/tsclientlib-connect-spike → crates/chanora_protocol/
New product code:
crates/chanora_protocol/src/{dto.rs,adapter.rs} — typed boundary
over tsclientlib. Tokio task owns the Connection; public
handle communicates via mpsc/oneshot. No tsclientlib types
cross out of the crate (SAD-067 / SysDes-011 / SysDes-029).
core/chanora_core/src/lib.rs — ChanoraSession composes the
protocol crate, enforces the DEC-006 single-connection
invariant.
crates/chanora_bridge/src/{api.rs,frb_generated.rs} — FRB 2.12.0
bridge per DEC-014. cdylib + staticlib + rlib. Typed
BridgeSnapshot / BridgeChannel / BridgeClient / BridgeError
DTOs. Process-wide OnceLock<Runtime> + OnceLock<ChanoraSession>.
flutter_rust_bridge.yaml at repo root.
apps/chanora_flutter/lib/main.dart — Alpha UI: server form,
connect button, channel tree, disconnect.
apps/chanora_flutter/lib/l10n/app_{en,zh}.arb expanded with the
Alpha key set; ARB metadata reaffirms ADR-008 for
server-provided content.
Generated Dart bindings under apps/chanora_flutter/lib/src/rust/.
Empirical verification (2026-05-14):
Workspace: cargo check + cargo test clean
(workspace tests: all green).
Bridge cdylib: target/release/libchanora_bridge.so produced
(~15 MB).
Flutter: flutter analyze clean; flutter test runs 3/3 green
including the alpha_e2e_test that drives the full
Dart → FRB → chanora_bridge → chanora_core → chanora_protocol
→ tsclientlib → UDP → cn.teamspeak.app
path. The captured logcat/stdout shows the tsproto resender
transitioning Connected → Disconnecting → Disconnected on
clean teardown.
Architecture changes:
- Removed the chanora_core ↔ chanora_bridge cyclic dependency.
chanora_core no longer knows the bridge exists; the bridge
maps from CoreError.
- chanora_bridge crate's #![forbid(unsafe_code)] lint relaxed
because FRB-generated glue legitimately uses unsafe at the
FFI boundary. Hand-written code remains unsafe-free.
Open follow-ups (NOT in this Alpha):
- Audio capture/playback wiring into chanora_audio
(Beta scope per DEC-001).
- Identity persistence via chanora_storage
(currently regenerated on every connect).
- Per-message diagnostics + redaction
(chanora_diagnostics still scaffold).
- Reconnect / network-loss recovery.
- Mobile (Android) build of the bridge cdylib + UI verification.