Skip to content

fix(worktree): show closing state instead of "Starting terminal…" - #164

Closed
neumie wants to merge 1 commit into
refactorx/full-headlessfrom
fix/worktree-close-starting-terminal
Closed

fix(worktree): show closing state instead of "Starting terminal…"#164
neumie wants to merge 1 commit into
refactorx/full-headlessfrom
fix/worktree-close-starting-terminal

Conversation

@neumie

@neumie neumie commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Problem

Closing a worktree left every pane painting the "Starting terminal…" placeholder for the whole teardown window — seconds on a real checkout — even though the terminals were already dead and the project was on its way out.

Root cause

prepare_background_worktree_removal (crates/okena-workspace/src/actions/worktree.rs:721) runs layout.clear_terminal_ids_except(&HashSet::new()): it nulls every layout leaf's terminal_id but deliberately keeps the project row and the tree shape standing, so ensure_terminal can't resurrect a PTY inside the doomed checkout.

The daemon then kills the PTYs (command_loop.rs:144) and only afterwards runs the blocking remove_dir_all; the project row is deleted much later (command_loop.rs:190). The client mirrors that intermediate snapshot verbatim, rebuilds each TerminalPane with terminal_id: None, create_new_terminal only logs a warning, and TerminalContent.terminal stays None → the uninitialized-slot placeholder at content.rs:605.

The id-nulling is correct and is not touched here.

Fix

  • okena-layout: new allocation-free LayoutNode::has_terminal_ids() predicate.
  • project_column.rs: the content branch becomes an explicit enum ColumnContent { Closing, Layout, Creating, Empty } picked by a pure column_content(), plus a render_closing_state placeholder mirroring the existing render_creating_state.

Closing has to win over Layout because a closing worktree keeps layout: Some — unlike is_creating, which sits in the else arm only because a mid-create project has layout: None.

Key call: Closing is gated on closing && !has_terminals, not on the closing flag alone. The merge and before_remove-hook phases also set is_closing while the terminals are still live and useful (and both are still abortable), so those keep painting the real pane tree. Only the id-less teardown window swaps to the placeholder.

Tests

  • 5 unit tests over the branch table: nulled-ids-while-closing, live-terminals-while-closing, closing bookmark with no layout, optimistic client flag, and a guard that the non-closing branches are unchanged.
  • 1 test in okena-layout pinning has_terminal_ids against clear_terminal_ids_except.

cargo build clean, clippy clean, 448 tests pass across okena-app / okena-layout / okena-workspace.

Notes

Based directly on #157 (refactorx/full-headless). This originally sat on top of #163; since that PR was closed unmerged, this branch was rebased with --onto to drop its three commits, so nothing from the closed PR rides along here.

Out of scope, spotted while tracing: command_loop.rs:237 (the JoinError arm of background removal) clears is_closing without the spawn_uninitialized_terminals restore its sibling git-failure arm performs. Panic-only path and recoverable in one click, so left alone here.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Y4Aq3X2s6ftafXzmqaN3A8

@neumie
neumie requested a review from matej21 July 21, 2026 08:53
@neumie
neumie force-pushed the fix/worktree-close-starting-terminal branch from ae26ae4 to 46fd18f Compare July 21, 2026 11:28
Closing a worktree left every pane painting the "Starting terminal…"
placeholder for the whole teardown window — seconds on a real checkout.

`prepare_background_worktree_removal` nulls every layout leaf's
`terminal_id` while keeping the project row and tree standing, so
`ensure_terminal` can't resurrect a PTY inside the doomed checkout. The
client mirrors that snapshot, rebuilds each pane with `terminal_id: None`,
and `TerminalContent` falls into its uninitialized-slot placeholder.

Give the project column an explicit `Closing` branch that wins over
`Layout`, since a closing worktree keeps `layout: Some`. Gated on the
terminals actually being gone rather than on the closing flag alone: the
merge and `before_remove`-hook phases also set the flag while the
terminals are still live and useful, and those keep painting.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y4Aq3X2s6ftafXzmqaN3A8
@neumie
neumie force-pushed the fix/worktree-close-starting-terminal branch from 46fd18f to 9bcf816 Compare July 21, 2026 12:46
@neumie
neumie changed the base branch from fix/sync-terminal-size to refactorx/full-headless July 21, 2026 12:46
@matej21

matej21 commented Jul 21, 2026

Copy link
Copy Markdown
Member

Integrated into #157 as commit d928be3 after review and combined validation.

@matej21 matej21 closed this Jul 21, 2026
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.

2 participants