Keep the claude pane prompt-only: compact height + auto transcript wipe
The pane was showing Claude Code's own transcript (tool calls, diffs) — redundant with the feed above. Two measures: - Default pane height is now compact (12 rows): room for the input box and status, (almost) none for transcript. Interactive grow to 75% is unchanged. - When a turn of the embedded session finishes streaming (Tap drop), schedule a ctrl-l into the PTY 400ms later: Claude Code clears the screen but keeps the conversation, leaving just the prompt UI. Residual: while a response is streaming, a few transcript rows can still scroll through the compact pane until the turn-end wipe fires. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
10
src/app.rs
10
src/app.rs
@@ -27,6 +27,10 @@ pub struct App {
|
||||
/// (AskUserQuestion/ExitPlanMode seen in the stream, answer not yet
|
||||
/// echoed back) — the UI gives the pane more rows while set.
|
||||
pub embed_grow: bool,
|
||||
/// When set, the UI should send ctrl-l to the embedded claude at this
|
||||
/// instant (a beat after a turn ends, once Claude Code has printed its
|
||||
/// final transcript lines) so the pane stays prompt-only.
|
||||
pub embed_clear_at: Option<Instant>,
|
||||
}
|
||||
|
||||
/// Client-side tools that render a large interactive UI in Claude Code.
|
||||
@@ -47,6 +51,7 @@ impl App {
|
||||
show_sessions: true,
|
||||
embed_session: None,
|
||||
embed_grow: false,
|
||||
embed_clear_at: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -338,6 +343,11 @@ impl Drop for Tap {
|
||||
if let Some(i) = self.cur.take() {
|
||||
s.entries[i].done = true;
|
||||
}
|
||||
// A turn of the embedded session just finished streaming: schedule a
|
||||
// transcript wipe shortly after Claude Code prints its final lines.
|
||||
if a.embed_session.as_deref() == Some(a.sessions[self.sidx].key.as_str()) {
|
||||
a.embed_clear_at = Some(Instant::now() + std::time::Duration::from_millis(400));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
12
src/term.rs
12
src/term.rs
@@ -137,6 +137,18 @@ impl EmbeddedTerm {
|
||||
});
|
||||
}
|
||||
|
||||
/// Ask Claude Code to wipe its transcript from the visible screen
|
||||
/// (ctrl-l clears the terminal but keeps the conversation; the live
|
||||
/// prompt UI redraws immediately). Used to keep the pane prompt-only —
|
||||
/// the feed above already shows everything the transcript would.
|
||||
pub fn clear_screen(&self) {
|
||||
let _ = self
|
||||
.term
|
||||
.lock()
|
||||
.unwrap()
|
||||
.key_down(KeyCode::Char('l'), KeyModifiers::CTRL);
|
||||
}
|
||||
|
||||
/// Forward a key press to the child. Returns false for keys we don't map.
|
||||
pub fn key(&self, k: ratatui::crossterm::event::KeyEvent) -> bool {
|
||||
use ratatui::crossterm::event::KeyCode as CK;
|
||||
|
||||
35
src/ui.rs
35
src/ui.rs
@@ -83,6 +83,24 @@ fn event_loop(
|
||||
) -> anyhow::Result<()> {
|
||||
loop {
|
||||
terminal.draw(|f| draw(f, &app, eui))?;
|
||||
// Scheduled transcript wipe (set by the tap when an embedded-session
|
||||
// turn finishes): keeps the pane prompt-only.
|
||||
let clear_due = {
|
||||
let mut a = app.lock().unwrap();
|
||||
if a.embed_clear_at.is_some_and(|t| std::time::Instant::now() >= t) {
|
||||
a.embed_clear_at = None;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
if clear_due {
|
||||
if let Some(et) = &eui.term {
|
||||
if !et.exited() {
|
||||
et.clear_screen();
|
||||
}
|
||||
}
|
||||
}
|
||||
if !event::poll(Duration::from_millis(33))? {
|
||||
continue;
|
||||
}
|
||||
@@ -193,15 +211,20 @@ fn event_loop(
|
||||
|
||||
fn draw(f: &mut Frame, app: &SharedApp, eui: &mut EmbedUi) {
|
||||
let mut a = app.lock().unwrap();
|
||||
// Embedded pane height: nothing when hidden; ~35% of the screen normally,
|
||||
// 75% while the tap says an interactive prompt is on screen — Claude
|
||||
// Code's question UI clips its option list to the rows it gets, so the
|
||||
// prompt wants as much height as we can spare.
|
||||
// Embedded pane height: nothing when hidden. The pane is meant to be
|
||||
// prompt-only (the feed above shows the context), so the default is just
|
||||
// tall enough for Claude Code's input box + status rows; while the tap
|
||||
// says an interactive prompt is on screen it grows to 75% so the option
|
||||
// list isn't clipped.
|
||||
const EMBED_COMPACT: u16 = 12; // 10 inner rows + borders
|
||||
let show_embed = eui.visible && eui.term.is_some();
|
||||
let embed_h = if show_embed {
|
||||
let total = f.area().height;
|
||||
let pct = if a.embed_grow { 75 } else { 35 };
|
||||
((total as u32 * pct / 100) as u16).clamp(10, total.saturating_sub(6))
|
||||
if a.embed_grow {
|
||||
((total as u32 * 75 / 100) as u16).clamp(10, total.saturating_sub(6))
|
||||
} else {
|
||||
EMBED_COMPACT.min(total.saturating_sub(6))
|
||||
}
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user