Skip to content

Commit 9de8f1d

Browse files
milankinenclaude
andcommitted
Monitor TUI: hierarchical q, broader selection-mode exit
Two small usability fixes: q on Monitor now closes an open details pane first and only falls through to Sandbox on a second press. Matches Esc/X behaviour for close and preserves "q = leave this screen" semantics one level up. Selection mode (mouse-uncaptured passthrough for native terminal copy) now exits on any keypress in the Sandbox tab, not just Esc / Ctrl+C. Key falls through to the normal handler so the typed char still reaches the shell. F2 (jump to Monitor) also re-captures so the user isn't stranded in passthrough on the click-driven Monitor. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 2b854fa commit 9de8f1d

1 file changed

Lines changed: 19 additions & 12 deletions

File tree

app/airlock-monitor/src/lib.rs

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -421,30 +421,34 @@ fn handle_key(
421421
kitty_enabled: bool,
422422
mouse_captured: &mut bool,
423423
) -> anyhow::Result<Option<i32>> {
424-
// Global shortcuts
424+
// Global shortcuts. F2 also exits selection mode since the Monitor
425+
// tab is entirely click-driven — leaving the user stuck in
426+
// passthrough-mouse on Monitor would be confusing.
425427
match (key.modifiers, key.code) {
426428
(_, KeyCode::F(1)) => {
427429
app.active_tab = Tab::Sandbox;
428430
return Ok(None);
429431
}
430432
(_, KeyCode::F(2)) => {
433+
if !*mouse_captured {
434+
crossterm::execute!(std::io::stdout(), crossterm::event::EnableMouseCapture)?;
435+
*mouse_captured = true;
436+
app.mouse_captured = true;
437+
}
431438
app.active_tab = Tab::Monitor;
432439
return Ok(None);
433440
}
434441
_ => {}
435442
}
436443

437-
// Auto-exit selection mode: Esc or Ctrl+C in the sandbox tab re-enables
438-
// mouse capture so the click-to-select flow is reversible.
439-
if app.active_tab == Tab::Sandbox
440-
&& !*mouse_captured
441-
&& (key.code == KeyCode::Esc
442-
|| (key.modifiers == KeyModifiers::CONTROL && key.code == KeyCode::Char('c')))
443-
{
444+
// Auto-exit selection mode: any keypress on the Sandbox tab
445+
// re-enables mouse capture. Fall through so the same key still
446+
// reaches the normal Sandbox handler below — the user intent is
447+
// "keep typing", not "eat this keystroke".
448+
if app.active_tab == Tab::Sandbox && !*mouse_captured {
444449
crossterm::execute!(std::io::stdout(), crossterm::event::EnableMouseCapture)?;
445450
*mouse_captured = true;
446451
app.mouse_captured = true;
447-
return Ok(None);
448452
}
449453

450454
match app.active_tab {
@@ -473,9 +477,15 @@ fn handle_key(
473477
}
474478
if app.monitor.network.details_open() {
475479
match key.code {
480+
// `q`, Esc, and X all close the details pane first,
481+
// staying on the Monitor tab. A second `q` from the
482+
// list view then goes back to Sandbox (below).
476483
KeyCode::Esc | KeyCode::Char('x' | 'X') => {
477484
app.monitor.network.close_details();
478485
}
486+
KeyCode::Char('q' | 'Q') if key.modifiers.is_empty() => {
487+
app.monitor.network.close_details();
488+
}
479489
KeyCode::Left | KeyCode::Right | KeyCode::Tab => {
480490
app.monitor.network.toggle_sub_tab();
481491
}
@@ -494,9 +504,6 @@ fn handle_key(
494504
.network
495505
.open_policy_dropdown(app.network.policy());
496506
}
497-
KeyCode::Char('q' | 'Q') if key.modifiers.is_empty() => {
498-
app.active_tab = Tab::Sandbox;
499-
}
500507
KeyCode::Char('d' | 'D') if key.modifiers.contains(KeyModifiers::CONTROL) => {
501508
let _ = sig_tx.blocking_send(1);
502509
let _ = sig_tx.blocking_send(15);

0 commit comments

Comments
 (0)