From 7e20d75f002f3f76216c615e0a4b67b5fd0b7ab9 Mon Sep 17 00:00:00 2001 From: didericis Date: Tue, 26 May 2026 15:25:22 -0400 Subject: [PATCH] feat(dashboard): focus right pane on Enter re-attach (in tmux) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Enter key on a focused agents-pane row is the operator's explicit "I want to interact with this agent" signal — after respawning the right pane with claude, move tmux's keyboard focus to that pane so the operator can start typing immediately. Without this, every Enter required a manual tmux nav (C-b →) to actually use the session. Mechanics: - `_attach_in_tmux` gains `focus_right_pane: bool = False`. - When True, runs `tmux select-pane -t ` after the respawn. - `_attach_to_bottle` (the Enter handler's helper) passes True. - Other callers (new-agent flow, stop's auto-attach) leave it False so the operator stays in the dashboard for follow-up navigation. `_tmux_select_pane` is a small subprocess wrapper, best-effort on failure. --- claude_bottle/cli/dashboard.py | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/claude_bottle/cli/dashboard.py b/claude_bottle/cli/dashboard.py index 6dcff5d..c82e2a7 100644 --- a/claude_bottle/cli/dashboard.py +++ b/claude_bottle/cli/dashboard.py @@ -995,12 +995,19 @@ def _attach_in_tmux( *, resume: bool, tmux_state: dict, + focus_right_pane: bool = False, ) -> str: """Spawn / respawn the right pane with `bottle`'s claude session. Mutates `tmux_state` ({'pane_id': str|None, 'slug': str|None}) so the main loop can track which slug is in the right pane (used by the agents-pane indicator + the - explicit-stop hook).""" + explicit-stop hook). + + `focus_right_pane=True` runs `tmux select-pane` after the + respawn so the operator is dropped into claude immediately. + The Enter re-attach key passes this; passive paths (the + auto-attach after a stop) leave it False so the operator + stays in the dashboard pane.""" docker_argv = bottle.claude_docker_argv( _claude_runtime_args(resume=resume), ) @@ -1011,9 +1018,24 @@ def _attach_in_tmux( # operator still gets a session. return _attach_via_handoff(stdscr, bottle, slug, resume=resume) tmux_state["slug"] = slug + if focus_right_pane: + _tmux_select_pane(pane_id) return f"[{slug}] in right pane" +def _tmux_select_pane(pane_id: str) -> None: + """`tmux select-pane -t ` — moves tmux's keyboard focus + to the pane. Best-effort; failure is silent (logged only via + subprocess's stderr, which we suppress).""" + try: + subprocess.run( + ["tmux", "select-pane", "-t", pane_id], + capture_output=True, text=True, check=False, + ) + except FileNotFoundError: + pass + + def _attach_to_bottle( stdscr: "curses._CursesWindow", bottle, @@ -1028,8 +1050,13 @@ def _attach_to_bottle( blocks until the operator exits claude. Re-attach always uses `--continue` — first attach happens via `_new_agent_flow`.""" if _in_tmux() and tmux_state is not None: + # Enter re-attach is an explicit "I want to interact with + # this agent" signal — move tmux focus to the right pane + # so keypresses land in claude instead of the dashboard. return _attach_in_tmux( - stdscr, bottle, slug, resume=True, tmux_state=tmux_state, + stdscr, bottle, slug, + resume=True, tmux_state=tmux_state, + focus_right_pane=True, ) return _attach_via_handoff(stdscr, bottle, slug, resume=True)