fix(domain,infrastructure,web-server,app-tauri,frontend): TUI native noire — route PTY explicite et replay du scrollback pré-attach sans perte — #360 (QA verte)

This commit is contained in:
2026-09-16 19:08:01 +02:00
parent a833af0fb5
commit 355a482fee
8 changed files with 220 additions and 13 deletions

View File

@ -113,6 +113,15 @@ impl Broadcast {
rx
}
/// Captures all output produced before attachment and installs the live
/// subscriber under the same lock. This is the no-gap handoff between the
/// retained history and future chunks.
fn snapshot_and_subscribe(&mut self) -> (Vec<u8>, Receiver<Vec<u8>>) {
let snapshot = self.snapshot();
let receiver = self.subscribe();
(snapshot, receiver)
}
/// Returns the retained scrollback as a contiguous byte vector.
fn snapshot(&self) -> Vec<u8> {
self.scrollback.iter().copied().collect()
@ -450,6 +459,23 @@ impl PtyPort for PortablePtyAdapter {
Ok(Box::new(rx.into_iter()))
}
fn subscribe_output_with_scrollback(
&self,
handle: &PtyHandle,
) -> Result<(Vec<u8>, OutputStream), PtyError> {
let map = self
.sessions
.lock()
.map_err(|_| PtyError::Io("pty registry poisoned".to_owned()))?;
let live = map.get(&handle.session_id).ok_or(PtyError::NotFound)?;
let (scrollback, rx) = live
.output
.lock()
.map_err(|_| PtyError::Io("pty output hub poisoned".to_owned()))?
.snapshot_and_subscribe();
Ok((scrollback, Box::new(rx.into_iter())))
}
fn scrollback(&self, handle: &PtyHandle) -> Result<Vec<u8>, PtyError> {
let map = self
.sessions
@ -555,6 +581,18 @@ mod tests {
let _second = hub.subscribe();
assert_eq!(hub.snapshot(), b"abc".to_vec());
}
#[test]
fn snapshot_and_subscribe_hands_off_scrollback_to_live_without_duplication() {
let mut hub = Broadcast::default();
hub.push(b"before");
let (snapshot, live) = hub.snapshot_and_subscribe();
hub.push(b"after");
assert_eq!(snapshot, b"before".to_vec());
assert_eq!(live.recv().unwrap(), b"after".to_vec());
}
}
/// **End-to-end PTY × Landlock enforcement (lot LP4-3).**