Lot B2 du chantier server/client mode : le cœur backend émet désormais ses
flux via une abstraction de sink agnostique, sans dépendre directement de
tauri::ipc::Channel, préalable au futur serveur web + PTY WebSocket.
- crates/backend : abstraction de sink (stream.rs) câblée dans lib.rs.
- crates/app-tauri : implémentation Tauri du sink (stream.rs) et adaptation
des surfaces lib.rs, pty.rs, chat.rs.
- Nettoyage clippy des 2 warnings B2.
Validé : cargo check --workspace vert, tests backend/app-tauri verts,
cœur agnostique Tauri, clippy B2 propre.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Each open/reattach spawned a pump thread feeding pty_bridge[session]. The
broadcast hub added a subscriber per attach without dropping the previous one,
and the stale pump thread kept delivering (its send_output still succeeded
against the new channel) — so every byte was delivered twice (more after
further re-attaches). Accumulated on each tab/layout/agent switch; window
resize made it glaring.
- Broadcast::subscribe() is now single-consumer: a new subscription supersedes
the previous one, ending the old pump thread's stream.
- PtyBridge gains a per-session generation; register() returns it and
unregister_if(session, gen) only removes when still current, so a dying
superseded thread can't tear down the channel that replaced it.
- All three attach sites (open/reattach/agent launch) use unregister_if.
Tests: 6 added (generation guard + single-consumer broadcast). Workspace green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>