# ESC Key Logic Flow

TunaCode TUI - Component Relationships and Event Flow

## Architecture Overview

The ESC key handling in TunaCode follows a priority cascade pattern with layered interception. Events flow through the Textual framework's event system, with modal screens having the highest priority, followed by the application-level handler.

Entry Point / Trigger
Decision Node
Action / Handler
Terminal State

## Primary ESC Event Flow

## Component Relationships

#### TunaApp (Main Application)

src/tunacode/ui/app.py

Central coordinator for all ESC handling. Defines global binding and priority cascade.

- Line 65: ESC binding with `priority=True`
- Lines 343-356: `action_cancel_request()`
- Owns references to: Editor, ShellRunner, request task

#### Editor Widget

src/tunacode/ui/widgets/editor.py

Input widget for user text. Provides `clear_input()` method called by app.

- Lines 110-113: `clear_input()`
- Line 54-55: `has_paste_buffer` property
- No direct ESC handling - delegated to app

#### ShellRunner

src/tunacode/ui/shell_runner.py

Manages shell command execution. Provides cancellation interface.

- Lines 119-137: `cancel()` method
- Line 111: "Esc to cancel" notification
- Terminates subprocess on cancel

#### Modal Screens

src/tunacode/ui/screens/*.py

Pushed onto screen stack. Each handles its own ESC to dismiss.

- ModelPickerScreen - two-phase ESC
- ProviderPickerScreen - two-phase ESC
- SessionPickerScreen - direct dismiss
- ThemePickerScreen - revert + dismiss

## Modal Two-Phase ESC Pattern

Model and Provider picker screens implement a two-phase ESC behavior for better UX:

1
User types filter text (e.g., "gpt" to filter models)
`self._filter_query = "gpt"`
|
2
First ESC press - clears filter, keeps modal open
`on_key(): if escape and _filter_query: clear filter, event.stop()`
|
3
Second ESC press - filter empty, dismisses modal
`action_cancel(): self.dismiss(None)`

### Implementation Location

# src/tunacode/ui/screens/model_picker.py:160-164 elif event.key == "escape" and self._filter_query: filter_input.value = "" self._filter_query = "" self._rebuild_options() event.stop() # Prevents propagation to action_cancel binding

## Complete File Reference

File | Lines | Component | ESC Behavior
--- | --- | --- | ---
`ui/app.py` | 65 | Binding | Global ESC binding with `priority=True`
`ui/app.py` | 343-356 | Handler | Priority cascade: request > shell > editor
`ui/widgets/editor.py` | 110-113 | Method | `clear_input()` - clears value and paste buffer
`ui/shell_runner.py` | 119-137 | Method | `cancel()` - terminates shell subprocess
`ui/screens/model_picker.py` | 94, 160-164 | ProviderPicker | Two-phase: clear filter, then dismiss
`ui/screens/model_picker.py` | 212, 289-293 | ModelPicker | Two-phase: clear filter, then dismiss
`ui/screens/session_picker.py` | 55, 179-181 | SessionPicker | Direct dismiss with `None`
`ui/screens/theme_picker.py` | 48, 87-90 | ThemePicker | Revert theme, dismiss with `None`
`ui/screens/update_confirm.py` | 46, 68-69 | UpdateConfirm | Dismiss with `False`
`ui/screens/setup.py` | 29, 100-102 | SetupScreen | Skip setup, dismiss with `False`

## Textual Event Propagation

## Key Takeaways

#### Priority Cascade Pattern

The `action_cancel_request()` method uses early returns to implement a clear priority order. First matching condition wins.

#### Modal Screen Isolation

Modal screens handle their own ESC independently. They intercept the event before it reaches the app layer via `event.stop()`.

#### No Hidden Chat State

The ChatContainer is always visible. ESC never hides the chat - it only cancels operations or clears input.

#### Two-Phase UX

Pickers with filters require two ESC presses: first clears filter, second dismisses. This prevents accidental closure.

Generated: 2026-02-02 | TunaCode TUI ESC Key Flow Documentation