feat(alpha): wire connect→snapshot→disconnect end-to-end (v0.1.0-alpha.1)
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.
This commit is contained in:
@@ -1,42 +1,73 @@
|
||||
//! # `chanora_bridge`
|
||||
//!
|
||||
//! Typed Flutter/Rust bridge. Owns the canonical DTO catalogue for
|
||||
//! commands (Dart → Rust), results (Rust → Dart return values), and
|
||||
//! events (Rust → Dart streams).
|
||||
//! Typed Flutter/Rust bridge — schema-controlled DTOs for commands,
|
||||
//! results, and events. Backed by `flutter_rust_bridge` 2.x per
|
||||
//! DEC-014.
|
||||
//!
|
||||
//! Bridge tool per DEC-014: `flutter_rust_bridge` 2.x. The bridge
|
||||
//! integration glue (codegen invocations, the cdylib boundary) is
|
||||
//! added when `apps/chanora_flutter` is wired up; this crate owns
|
||||
//! only the type definitions and error mappings — explicitly *not*
|
||||
//! `tsclientlib` or raw subsystem types (SAD-067, SDD-079).
|
||||
//! ## Alpha scope
|
||||
//!
|
||||
//! ## Status
|
||||
//! Exposes three commands that target the Alpha release goal:
|
||||
//!
|
||||
//! Scaffold only.
|
||||
//! * `bridge_init()` — one-time process initialisation. Sets up
|
||||
//! logging.
|
||||
//! * `connect(host, nickname)` — connects to a TS3-compatible
|
||||
//! server and returns a typed [`ConnectResultDto`] containing the
|
||||
//! server snapshot.
|
||||
//! * `disconnect()` — clean disconnect.
|
||||
//! * `snapshot()` — re-fetch the current server snapshot.
|
||||
//!
|
||||
//! No audio commands cross the bridge in Alpha; audio is Beta scope.
|
||||
//!
|
||||
//! ## Boundary discipline
|
||||
//!
|
||||
//! Every type in this module is `Serialize + Deserialize` over owned
|
||||
//! primitives or `String`s. No `tsclientlib`, `cpal`, or backend
|
||||
//! types may appear in the public surface (SAD-067, SDD-079).
|
||||
//!
|
||||
//! Note: this crate cannot use `#![forbid(unsafe_code)]` because the
|
||||
//! FRB-generated glue (in `frb_generated`) legitimately uses unsafe
|
||||
//! for the FFI boundary. Hand-written code in this crate must
|
||||
//! nonetheless avoid `unsafe` and is held to that standard by review.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
pub mod api;
|
||||
mod frb_generated;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors raised at the bridge boundary. Production code must keep
|
||||
/// `BridgeError` user-safe — no secrets, no protocol details, no
|
||||
/// path information beyond what the redaction policy permits.
|
||||
#[derive(Debug, Error, Clone, Serialize, Deserialize)]
|
||||
/// these user-safe — no secrets, no protocol details, no path
|
||||
/// information beyond what the redaction policy permits.
|
||||
#[derive(Debug, Error, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub enum BridgeError {
|
||||
/// The caller submitted a malformed command DTO.
|
||||
#[error("invalid command: {0}")]
|
||||
InvalidCommand(String),
|
||||
/// An unknown / unmapped error escaped the subsystem boundary.
|
||||
/// Production callers should never see this; if it appears it is
|
||||
/// a mapping bug in this crate.
|
||||
/// Connection layer failure (typed-mapped from CoreError).
|
||||
#[error("connection: {0}")]
|
||||
Connection(String),
|
||||
/// Operation requires an active connection.
|
||||
#[error("not connected")]
|
||||
NotConnected,
|
||||
/// Already connected; DEC-006 forbids a second concurrent
|
||||
/// connection.
|
||||
#[error("already connected")]
|
||||
AlreadyConnected,
|
||||
/// An unmapped error escaped the subsystem boundary. Production
|
||||
/// callers should never see this; if they do, it is a mapping
|
||||
/// bug here.
|
||||
#[error("unmapped: {0}")]
|
||||
Unmapped(String),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn it_compiles() {}
|
||||
impl From<chanora_core::CoreError> for BridgeError {
|
||||
fn from(e: chanora_core::CoreError) -> Self {
|
||||
match e {
|
||||
chanora_core::CoreError::NotConnected => BridgeError::NotConnected,
|
||||
chanora_core::CoreError::AlreadyConnected => BridgeError::AlreadyConnected,
|
||||
chanora_core::CoreError::Protocol(p) => BridgeError::Connection(format!("{p}")),
|
||||
other => BridgeError::Unmapped(format!("{other}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user