fix: eliminate alt-screen flash when returning from repo/project detail

The hub hands its live terminal to detail/project_detail on open (already
flash-free going in), but both sub-views unconditionally tore the terminal
down on exit; the hub's next loop iteration then always called tui::setup()
again, producing a real LeaveAlternateScreen -> (plain terminal visible for
one frame) -> EnterAlternateScreen blink on every trip back.

- detail::run / project_detail::run now return the live terminal on a clean
  exit instead of tearing it down (only torn down if the inner loop itself
  errors); hub::tui_phase accepts an already-live terminal and skips its own
  setup() when given one, threading it across loop iterations
- project_detail's own nested repo-detail call (Enter on its Repos tab) had
  the same issue one level down, fixed the same way via Option<Tty> so the
  terminal can be taken out for the nested call and put back
- verified via raw pty byte counts: two full hub<->detail round trips now
  show zero EnterAlternateScreen/LeaveAlternateScreen pairs in between,
  vs. one pair per trip before
This commit is contained in:
dualfroz committed 2026-07-07 17:58:02 +02:00
1 parent 14ef7fb06c
commit a3658d5e97
4 files changed
+64 -22

No files matched your search

+10 -2
View File
@@ -148,8 +148,16 @@ pub fn maybe_load_activity(state: &mut HubState) {
/// event, and routes it to whichever input mode is currently active. Modal/
/// confirm/filter input short-circuit here (they're loop control flow, not
/// part of the global keymap); everything else goes to `keys_dispatch`.
pub fn tui_phase(state: &mut HubState) -> Result<(PhaseResult, Option<crate::tui::Tty>)> {
let mut terminal = crate::tui::setup()?;
///
/// `existing_term` lets the caller hand back a terminal a sub-view (repo/
/// project detail) just returned instead of tearing down and setting up a
/// fresh one — the previous "leave alt screen, blink the real terminal,
/// re-enter" was visible for a frame every time detail handed control back.
pub fn tui_phase(state: &mut HubState, existing_term: Option<crate::tui::Tty>) -> Result<(PhaseResult, Option<crate::tui::Tty>)> {
let mut terminal = match existing_term {
Some(t) => t,
None => crate::tui::setup()?,
};
let result = (|| -> Result<PhaseResult> {
loop {
state.poll_incoming();