Fix wide-char (emoji/CJK) column drift through the whole render chain

Wide characters shifted everything after them one column to the right:

- render.rs: vt100's empty wide-continuation cell was padded to a
  space, making wide chars occupy 2+1 columns — skip it entirely
- ipc.rs buffer_to_ansi: same bug for daemon frames — skip cells
  covered by a preceding wide symbol (unicode-width)
- app.rs: clamp virtual terminals to >= 2 columns (vt100 0.16 has a
  subtraction underflow panic on wide chars in a 1-col grid) and give
  content_area a sane 80x24 default so windows spawned before the
  first layout pass aren't degenerate
- main.rs: use try_send for display frames so one slow/stalled client
  can no longer freeze the whole WM
- integration test: new end-to-end check that emoji do not shift the
  row, plus fix a false match against the window title

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 09:33:06 +02:00
parent 5ca8eb4647
commit 6aee7ce00a
7 changed files with 63 additions and 5 deletions

View File

@@ -185,7 +185,14 @@ fn run_standalone_loop(
};
all_clients.retain(|_, (role, _, _, tx)| {
if *role == ClientRole::Display {
tx.send(frame_msg.clone()).is_ok()
// try_send: en långsam/hängd display-klient får INTE
// blockera hela WM:et — droppa framen till den klienten
// och koppla bara bort vid stängd kanal.
match tx.try_send(frame_msg.clone()) {
Ok(()) => true,
Err(std::sync::mpsc::TrySendError::Full(_)) => true,
Err(std::sync::mpsc::TrySendError::Disconnected(_)) => false,
}
} else {
true
}
@@ -344,7 +351,14 @@ fn run_daemon_mode() -> io::Result<()> {
};
all_clients.retain(|_, (role, _, _, tx)| {
if *role == ClientRole::Display {
tx.send(frame_msg.clone()).is_ok()
// try_send: en långsam/hängd display-klient får INTE
// blockera hela WM:et — droppa framen till den klienten
// och koppla bara bort vid stängd kanal.
match tx.try_send(frame_msg.clone()) {
Ok(()) => true,
Err(std::sync::mpsc::TrySendError::Full(_)) => true,
Err(std::sync::mpsc::TrySendError::Disconnected(_)) => false,
}
} else {
true
}