Sort le serveur HTTP/WS de `app-tauri` vers un crate `web-server` autonome,
exposant le binaire `idea-serve` qui sert le client web sans aucune dépendance
GUI. Le cœur (use cases, DTO, events) devient partagé entre deux driving
adapters : le desktop Tauri et le serveur headless.
Structure :
- `crates/web-server` : lib + bin `idea-serve`, consomme `backend::{dto, events}`.
- `crates/backend` : `dto.rs` et `events.rs` deviennent le propriétaire canonique
des DTO/events transport-neutres (arbitrage Architect : pas de crate contrat
dédié, `backend` est déjà le composition root transport-neutre).
- `crates/app-tauri` : `dto.rs` réduit à un shim de ré-export, `events.rs` réduit
au relais Tauri (souscription au bus + emit), `server.rs` vidé au profit du
crate partagé.
- `Cargo.toml`/`Cargo.lock` : `web-server` ajouté aux membres du workspace.
Frontière respectée : les DTO de `backend` ne tirent ni Tauri, ni HTTP/WS, ni
Axum/Hyper, ni UI ; `domain`/`application` n'en dépendent pas. Le frontend est
inchangé (`git diff 506d589...HEAD -- frontend` vide).
`web_server::run_embedded(...)` est le point d'extension prévu pour #68 (le
desktop consommera `web-server` comme lib plutôt que d'en forker le serveur).
QA (exécution réelle, re-vérifiée avant merge) :
- `cargo test -p backend` 41 passed · `-p web-server` 55 passed · `-p app-tauri`
35 passed, 0 échec.
- Invariant headless : `ldd target/debug/idea-serve` ne tire aucun
webkit/javascriptcore/gtk/gdk/soup/tauri/wry, là où `app-tauri` les tire bien.
- Validation live utilisateur : `idea-serve` démarre, sert le SPA, le contrôle
d'origine strict tient, accès distant OK via reverse proxy.
- Les 10 échecs `openai_compat` de `cargo test --workspace` sont préexistants sur
`506d589` (bind réseau interdit par la sandbox), hors périmètre.
Squash de `82e8e77` (commit de sûreté « état intermédiaire non figé », qui
rapatriait le travail réalisé par erreur sur la branche de #69) et de `ddbea7b`
(déduplication des DTO events, comblant l'écart annoncé par le premier). Les deux
n'avaient de sens qu'ensemble : ce commit fige le contrat que le premier laissait
explicitement ouvert. Arbre identique bit pour bit à `ddbea7b` ; historique
d'origine conservé sur `backup/ticket65-pre-squash-ddbea7b`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
70 lines
2.7 KiB
Rust
70 lines
2.7 KiB
Rust
//! Tauri event relay.
|
|
//!
|
|
//! The stable serialisable event DTOs live in `backend::events`. This module
|
|
//! owns only the Tauri-specific relay: subscribing to the domain event bus and
|
|
//! emitting named Tauri events.
|
|
|
|
pub use backend::events::{
|
|
AgentLaunchFailedDto, DomainEventDto, ModelServerStatusChangedDto, OrchestrationSourceDto,
|
|
AGENT_LAUNCH_FAILED, DOMAIN_EVENT, MODEL_SERVER_STATUS_CHANGED,
|
|
};
|
|
use domain::events::DomainEvent;
|
|
use infrastructure::TokioBroadcastEventBus;
|
|
use tauri::{AppHandle, Emitter};
|
|
|
|
/// Subscribes the relay to the bus and spawns a background task that forwards
|
|
/// every non-PTY [`DomainEvent`] to the frontend as Tauri events.
|
|
///
|
|
/// High-frequency `PtyOutput` is intentionally not relayed through this global
|
|
/// event; it goes through per-session PTY channels instead.
|
|
pub fn spawn_relay(app: AppHandle, bus: &TokioBroadcastEventBus) {
|
|
use tokio::sync::broadcast::error::RecvError;
|
|
|
|
let mut rx = bus.raw_receiver();
|
|
tauri::async_runtime::spawn(async move {
|
|
loop {
|
|
match rx.recv().await {
|
|
Ok(event) => {
|
|
if matches!(event, DomainEvent::PtyOutput { .. }) {
|
|
continue;
|
|
}
|
|
|
|
let dto = DomainEventDto::from(&event);
|
|
let _ = app.emit(DOMAIN_EVENT, dto);
|
|
|
|
match &event {
|
|
DomainEvent::ModelServerStatusChanged { server_id, status } => {
|
|
let _ = app.emit(
|
|
MODEL_SERVER_STATUS_CHANGED,
|
|
ModelServerStatusChangedDto {
|
|
server_id: server_id.to_string(),
|
|
status: status.into(),
|
|
},
|
|
);
|
|
}
|
|
DomainEvent::AgentLaunchFailed {
|
|
agent_id,
|
|
cause,
|
|
code,
|
|
message,
|
|
} => {
|
|
let _ = app.emit(
|
|
AGENT_LAUNCH_FAILED,
|
|
AgentLaunchFailedDto {
|
|
agent_id: agent_id.to_string(),
|
|
cause: cause.clone(),
|
|
code: code.clone(),
|
|
message: message.clone(),
|
|
},
|
|
);
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
Err(RecvError::Lagged(_)) => continue,
|
|
Err(RecvError::Closed) => break,
|
|
}
|
|
}
|
|
});
|
|
}
|