Skip to content

feat(ui): add terminal reconnection on daemon disconnect#641

Open
Wirasm wants to merge 1 commit intomainfrom
feat/ui-terminal-reconnect
Open

feat(ui): add terminal reconnection on daemon disconnect#641
Wirasm wants to merge 1 commit intomainfrom
feat/ui-terminal-reconnect

Conversation

@Wirasm
Copy link
Owner

@Wirasm Wirasm commented Mar 19, 2026

Summary

  • Add ReconnectState enum (Idle, Connecting, Failed) and daemon_session_id field to Terminal
  • Add Reconnect GPUI action on TerminalView triggered by R key when terminal has error
  • Reconnect handler: spawns async task calling connect_for_attach(), builds new Terminal::from_daemon(), replaces terminal + event task atomically
  • Update error banner to show reconnect status ("Press R to reconnect", "Reconnecting...", "Reconnect failed: {msg}")
  • Edge cases: daemon gone (clear error), session destroyed (SessionNotFound), multiple R presses (debounced), local terminals (ignored)

Test plan

  • cargo fmt --check passes
  • cargo clippy --all -- -D warnings passes
  • cargo test --all passes
  • cargo build --all passes
  • Manual: open UI → create daemon session → kill daemon → press R → verify reconnection

When the daemon reader connection drops, the terminal view now shows
"Press R to reconnect" instead of a dead-end error. The Reconnect action
spawns an async task that calls connect_for_attach(), builds a new
Terminal::from_daemon(), and replaces the terminal + event task
atomically. Handles edge cases: daemon gone, session destroyed, multiple
rapid presses, local terminals unaffected.
@Wirasm
Copy link
Owner Author

Wirasm commented Mar 19, 2026

PR Review Summary

Critical Issues (0 found)

No critical issues found.

Important Issues (2 found)

Agent Issue Location
code-reviewer Reconnect uses hardcoded 24x80 dimensions. connect_for_attach(&sid, 24, 80) ignores the terminal's current dimensions. After reconnect, the daemon session starts with default size until the next prepaint triggers a resize. This causes a visible content reflow flash. Consider reading current_size from the old terminal before spawning the reconnect task and passing it through. terminal_view.rs:243
silent-failure-hunter reconnect_state lock failure is silently swallowed. set_reconnect_state() does if let Ok(mut s) = self.reconnect_state.lock() — if the mutex is poisoned, the state update is silently dropped with no logging. Every other lock-failure path in state.rs logs an error (see set_error_state, error_message, etc.). Add a tracing::error on the Err branch for consistency. state.rs:834-837

Suggestions (4 found)

Agent Suggestion Location
type-design-analyzer daemon_session_id could use SessionId newtype. The field is Option<String>, but the codebase has a SessionId newtype in kild-protocol. Using Option<SessionId> would prevent accidentally passing a branch name where a session ID is expected. The from_daemon constructor already receives session_id: String so this would require updating the signature too — consider if the type safety is worth the churn. state.rs:236
code-simplifier reconnect_state could be AtomicCell or simpler. The Arc<Mutex<ReconnectState>> is only read/written from the main thread (view layer) — never from background tasks. The Arc sharing between Terminal and the view layer is unused since set_reconnect_state/reconnect_state are only called on &self/&self through the owned view.terminal. A plain Cell<ReconnectState> or even a direct field on TerminalView (rather than on Terminal) would be simpler and avoid the mutex overhead. state.rs:238
code-reviewer Consider also accepting uppercase R. The reconnect keybinding only fires on lowercase r (key == "r"). If CapsLock is on, the key would be "R" and reconnect wouldn't trigger, which could confuse users. Consider key.eq_ignore_ascii_case("r") or matching both. terminal_view.rs:336
docs-impact-agent CLAUDE.md terminal section is not affected. The existing documentation does not reference reconnection behavior, and this is a UI-internal feature. No doc updates needed. N/A

Strengths

  • Clean separation: reconnect state lives on Terminal, reconnect logic lives on TerminalView — follows existing ownership patterns
  • Proper debouncing: ReconnectState::Connecting check prevents multiple in-flight reconnects
  • Old terminal cleanup is correct: swapping view.terminal drops the old Terminal, which sends Detach (best-effort) and cancels old tasks via _event_task replacement
  • Good tracing coverage: _started, _completed, _failed events follow the project's structured logging convention
  • Banner UX is well-considered: different messages for idle/connecting/failed states with retry hint

Verdict

READY TO MERGE (with minor fixes recommended)

Recommended Actions

  1. Pass actual terminal dimensions to connect_for_attach instead of hardcoded 24x80
  2. Add tracing on poisoned lock in set_reconnect_state
  3. Consider suggestions at your discretion — none are blocking

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant