feat(hh): /drive command (mobile-friendly) + Esc no longer quits

F2 is unreachable on phone/Termux keyboards, so add a /drive chat command to
enter sandbox drive mode (type into the shared shell; Esc releases). F2 still
works on desktop. Esc no longer quits from chat mode (footgun on mobile) — quit
is Ctrl-Q only. Updated on-screen hints + sandbox pane title.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
leetcrypt 2026-05-30 22:03:00 -07:00
parent 064ee67162
commit 1445b1151a
2 changed files with 16 additions and 6 deletions

View File

@ -135,7 +135,7 @@ impl App {
self.users = users; self.users = users;
self.connected = true; self.connected = true;
self.sys(format!("joined as {}", self.me)); self.sys(format!("joined as {}", self.me));
self.sys("/send <file> · /sendd <dir> · /sbx launch · F2 drive · ctrl-q quit"); self.sys("/sbx launch · /drive (type in shell, Esc to release) · /send <file> · ctrl-q quit");
} }
Net::Message(l) => self.lines.push(l), Net::Message(l) => self.lines.push(l),
Net::Roster { users, capacity } => { Net::Roster { users, capacity } => {
@ -391,7 +391,6 @@ pub async fn run(session: Session, theme: Theme) -> Result<()> {
} }
} else { } else {
match k.code { match k.code {
KeyCode::Esc => break Ok(()),
KeyCode::Enter => { KeyCode::Enter => {
let line = app.input.trim().to_string(); let line = app.input.trim().to_string();
app.input.clear(); app.input.clear();
@ -507,7 +506,17 @@ fn handle_command(
term: &Terminal<CrosstermBackend<std::io::Stdout>>, term: &Terminal<CrosstermBackend<std::io::Stdout>>,
) { ) {
let room = &session.room; let room = &session.room;
if let Some(path) = line.strip_prefix("/sendd ").or_else(|| line.strip_prefix("/send ")) { if line == "/drive" {
// Mobile-friendly alternative to F2 (no function key needed).
if app.sandbox.is_none() {
app.sys("no sandbox running — /sbx launch first");
} else if app.can_drive() {
app.driving = true;
app.sys("⛧ drive mode ON — type into the shell · press Esc to release");
} else {
app.sys("you don't have drive permission — the owner can /grant you");
}
} else if let Some(path) = line.strip_prefix("/sendd ").or_else(|| line.strip_prefix("/send ")) {
let path = path.trim(); let path = path.trim();
match ft::read_payload(path) { match ft::read_payload(path) {
Ok((name, bytes, dir)) => { Ok((name, bytes, dir)) => {

View File

@ -46,9 +46,9 @@ fn draw_sandbox(f: &mut Frame, area: ratatui::layout::Rect, app: &App, theme: &T
.map(|r| Line::from(Span::styled(r, Style::default().fg(theme.title)))) .map(|r| Line::from(Span::styled(r, Style::default().fg(theme.title))))
.collect(); .collect();
let drive = if app.driving { let drive = if app.driving {
" · DRIVING (esc to release)" " · DRIVING — type here · Esc to release"
} else { } else {
" · F2 to drive" " · type /drive (or F2) to take the shell"
}; };
let title = format!(" sandbox · {}{} ", sv.backend, drive); let title = format!(" sandbox · {}{} ", sv.backend, drive);
let border = if app.driving { theme.accent } else { theme.border }; let border = if app.driving { theme.accent } else { theme.border };
@ -157,7 +157,8 @@ fn draw_input(f: &mut Frame, area: ratatui::layout::Rect, app: &App, theme: &The
.title(Span::styled( .title(Span::styled(
match &app.pending_offer { match &app.pending_offer {
Some(o) => format!(" ⛧ incoming: {} — /accept or /reject ", o.name), Some(o) => format!(" ⛧ incoming: {} — /accept or /reject ", o.name),
None => " message · enter send · esc quit ".to_string(), None if app.driving => " ⛧ DRIVING the shell — Esc to release ".to_string(),
None => " message · enter send · /drive for shell · ctrl-q quit ".to_string(),
}, },
Style::default().fg(theme.title), Style::default().fg(theme.title),
)), )),