fix(tui): add post-turn screen clear + redraw to eliminate output bleeding

The run_turn_to buffer + emit_output=false was correctly wired,
but runtime internals (child process stderr, tokio tracing, or
unstructured error paths) can still write to the real terminal fd.

Added a crossterm Clear(All) + MoveTo(0,0) after each turn to wipe
any stdout debris that may have landed on the alternate screen,
followed by a full TUI redraw to re-render both panes cleanly.

This is the belt-and-suspenders approach: buffer captures what we can,
screen clear catches what we can't.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
TheArchitectit 2026-06-12 10:48:27 -05:00
parent 9ed9df543b
commit 99c4650ac7
2 changed files with 88 additions and 10 deletions

73
rebase-upstream.sh Executable file
View File

@ -0,0 +1,73 @@
#!/usr/bin/env bash
# Rebase claw-code feat-tui onto latest upstream/main (ultracode)
# Run from repo root: ./rebase-upstream.sh
#
# Prereq: remotes configured
# upstream → ultracode/claw (or whatever the upstream org/repo is)
# origin → your fork
set -euo pipefail
BRANCH="feat-tui"
REMOTE_UPSTREAM="upstream"
REMOTE_ORIGIN="origin"
cd "$(git rev-parse --show-toplevel)"
echo "🔄 Rebasing $BRANCH onto $REMOTE_UPSTREAM/main"
# Make sure we're on the right branch
current=$(git branch --show-current)
if [ "$current" != "$BRANCH" ]; then
echo "❌ Not on $BRANCH (on $current). Switch first."
exit 1
fi
# Stash any uncommitted work
if ! git diff --quiet || ! git diff --cached --quiet; then
echo "📦 Stashing uncommitted changes..."
git stash push -m "auto-stash before rebase $(date +%Y%m%d-%H%M%S)"
STASHED=true
else
STASHED=false
fi
# Fetch upstream
echo "⬇️ Fetching $REMOTE_UPSTREAM..."
git fetch "$REMOTE_UPSTREAM"
# Check if upstream/main moved
OLD_BASE=$(git merge-base "$BRANCH" "$REMOTE_UPSTREAM/main")
NEW_HEAD=$(git rev-parse "$REMOTE_UPSTREAM/main")
if [ "$OLD_BASE" = "$NEW_HEAD" ]; then
echo "✅ Already up to date with $REMOTE_UPSTREAM/main ($NEW_HEAD)"
else
echo "📋 Rebasing... ($OLD_BASE$NEW_HEAD)"
echo " $(git log --oneline $OLD_BASE..$NEW_HEAD | wc -l) new upstream commits"
if ! git rebase "$REMOTE_UPSTREAM/main"; then
echo ""
echo "⚠️ CONFLICTS! Resolve them, then:"
echo " git add <resolved files>"
echo " git rebase --continue"
echo ""
echo " To abort: git rebase --abort"
exit 1
fi
echo "✅ Rebase complete"
fi
# Force push to origin (safe — only your fork)
echo "⬆️ Force-pushing to $REMOTE_ORIGIN/$BRANCH..."
git push --force-with-lease "$REMOTE_ORIGIN" "$BRANCH"
# Restore stashed work
if [ "$STASHED" = true ]; then
echo "📦 Restoring stashed changes..."
git stash pop
fi
echo ""
echo "🎉 Done! $BRANCH is now rebased on $REMOTE_UPSTREAM/main"
echo " HEAD: $(git log --oneline -1)"
echo " $(git log --oneline "$REMOTE_UPSTREAM/main".."$BRANCH" | wc -l) commits ahead of upstream"

View File

@ -7301,18 +7301,20 @@ fn run_tui_repl(mut cli: LiveCli) -> Result<(), Box<dyn std::error::Error>> {
let mut buf: Vec<u8> = Vec::new();
let result = cli.run_turn_to(&trimmed, &mut buf, false);
// Feed the captured output into the conversation pane.
// The buffer may contain ANSI codes from TerminalRenderer —
// strip them before pushing.
let captured = String::from_utf8_lossy(&buf);
let plain = tui_update::strip_ansi(&captured);
if !plain.is_empty() {
app.push_output(&plain, false);
// Wipe the alternate screen in case any runtime internals
// wrote to the real stdout fd despite emit_output=false
// (e.g. child process stderr, tokio tracing, etc.)
{
use crossterm::terminal::ClearType;
let _ = crossterm::execute!(
std::io::stdout(),
crossterm::terminal::Clear(ClearType::All),
crossterm::cursor::MoveTo(0, 0),
);
}
// Also read the last assistant message from the session for
// the conversation pane (richer content than the spinner/status
// lines in the buffer).
// Read the last assistant message from the session for
// the conversation pane.
{
let messages = &cli.runtime.session().messages;
if let Some(msg) = messages.last() {
@ -7336,6 +7338,9 @@ fn run_tui_repl(mut cli: LiveCli) -> Result<(), Box<dyn std::error::Error>> {
}
}
update_dashboard(&dashboard_state, &cli);
// Force a full clear+redraw to ensure the conversation pane
// is correctly bounded after each turn
let _ = app.redraw_after_turn();
}
tui::TuiReadOutcome::Cancel => {
// Clear input, stay in TUI