Repository navigation
[CTX-1336] fix(ui): remove ghost host leaf and prune emptied workspace on float teardown (#2222 sibling) #2224
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1526,9 +1526,30 @@ impl TerminalApp { | |
| if self.runtime.floated_views().contains(&focused) | ||
| || self.runtime.pinned_views().contains(&focused) | ||
| { | ||
| // CTX-1336 (#2221): a float can be the active | ||
| // workspace's last live view (its tiled base closed). | ||
| // Discarding it must not strand a view-less slot: prune | ||
| // the emptied workspace exactly like the CTX-1297 | ||
| // last-tiled-panel path, so focus follows a live | ||
| // workspace instead of a blank surface. | ||
| let prune_after_discard = self.runtime.floated_views() == vec![focused] | ||
| && self.runtime.layout().leaf_count() == 0 | ||
| && self.runtime.workspace_count() > 1; | ||
| if self.runtime.discard_floating_view(focused) { | ||
| eprintln!("bitty: keymap close_view -> discarded floating {focused:?}"); | ||
| } | ||
| if prune_after_discard { | ||
| let workspace = self.runtime.active_workspace_index(); | ||
| if self.runtime.workspace_close_at(workspace).is_ok() { | ||
| eprintln!( | ||
| "bitty: keymap close_view -> last float closed, empty workspace pruned" | ||
| ); | ||
| } else { | ||
| eprintln!( | ||
| "warning: keymap close_view could not prune empty workspace — ignoring" | ||
| ); | ||
| } | ||
| } | ||
| eprintln!( | ||
| "bitty: keymap close_view -> leafs={} focused={:?}", | ||
| self.runtime.leaf_count(), | ||
|
|
@@ -1555,6 +1576,43 @@ impl TerminalApp { | |
| "warning: keymap close_view could not prune empty workspace — ignoring" | ||
| ); | ||
| } | ||
| } else if self.runtime.floated_count() > 0 { | ||
| // CTX-1336 (#2221): a detached float survives in | ||
| // this workspace, so the closed tiled host is a | ||
| // pane close. Tearing down only its session kept a | ||
| // session-less leaf that stayed focused: the empty | ||
| // leaf painted a cursor ghost in the vacated region | ||
| // and input was silently dropped (no focused writer, | ||
| // no primary owner). Remove the leaf and hand focus | ||
| // to the live float instead. | ||
| let was_primary = self.runtime.is_primary_view(&focused); | ||
| if was_primary { | ||
| if self.runtime.close_primary_session() { | ||
| eprintln!( | ||
| "bitty: keymap close_view tore down primary shell {focused:?} (float base drained)" | ||
| ); | ||
| } | ||
| } else if self.runtime.close_pane_session(&focused) { | ||
| eprintln!( | ||
| "bitty: keymap close_view tore down last tiled panel shell {focused:?} (float base drained)" | ||
| ); | ||
| } | ||
| // A bare last leaf must go: the tiling-first helper | ||
| // refuses it, the layout-level remove yields the | ||
| // empty base that the float composite expects | ||
| // (G1/DEC-0099: the float stays a float). | ||
| let mut layout = self.runtime.layout().clone(); | ||
| if layout.remove_leaf(focused).is_some() { | ||
| self.runtime.set_layout_closing(layout, focused); | ||
| } | ||
| if let Some(survivor) = self.runtime.floated_views().last().copied() { | ||
| self.runtime.set_focus(survivor); | ||
| } | ||
|
Comment on lines
+1603
to
+1610
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: rg -n 'NewPanel|NewSplit|split_focused_leaf|set_layout_closing|float.only' crates/bitty-terminal/src/chrome_keys.rs crates/bitty-terminal/src/tests.rs
sed -n '530,615p' crates/bitty-terminal/src/chrome_keys.rs
sed -n '1570,1625p' crates/bitty-terminal/src/chrome_keys.rsRepository: bitty-terminal/bitty Length of output: 10561 🏁 Script executed: set -o pipefail
printf '%s\n' '--- action dispatch ---'
sed -n '1080,1435p' crates/bitty-terminal/src/chrome_keys.rs
printf '%s\n' '--- close branch continuation ---'
sed -n '1435,1685p' crates/bitty-terminal/src/chrome_keys.rs
printf '%s\n' '--- related tests ---'
sed -n '4380,4545p' crates/bitty-terminal/src/chrome_keys.rs
sed -n '5140,5325p' crates/bitty-terminal/src/chrome_keys.rs
printf '%s\n' '--- float teardown tests ---'
sed -n '9550,9870p' crates/bitty-terminal/src/tests.rs
printf '%s\n' '--- runtime symbols ---'
rg -n -F --glob '*.rs' -e 'panel_bisect_target' -e 'set_layout_closing' -e 'layout_closing' -e 'create.*panel' -e 'new.*panel' crates/bitty-runtime crates/bitty-terminal | head -240Repository: bitty-terminal/bitty Length of output: 42506 🏁 Script executed: set -o pipefail
printf '%s\n' '--- exact runtime focus/layout methods ---'
sed -n '2060,2145p' crates/bitty-runtime/src/runtime/layout_focus.rs
sed -n '2470,2555p' crates/bitty-runtime/src/runtime/layout_focus.rs
printf '%s\n' '--- NewPanel/NewSplit references and contract text ---'
rg -n -i --glob '!target/**' -e 'new_panel' -e 'new split' -e 'NewPanel' -e 'NewSplit' -e '2223' -e 'no.op' -e 'float.only' -e 'empty.*layout' crates docs README.md .github 2>/dev/null || true
printf '%s\n' '--- action configuration definitions ---'
rg -n -F --glob '*.rs' -e 'enum ChromeAction' -e 'NewPanel' -e 'NewSplit' crates/bitty-config crates/bitty-terminal crates/bitty-runtime
printf '%s\n' '--- focused NewPanel/NewSplit tests ---'
sed -n '5080,5335p' crates/bitty-terminal/src/chrome_keys.rs
sed -n '8250,8370p' crates/bitty-terminal/src/tests.rsRepository: bitty-terminal/bitty Length of output: 41836 🏁 Script executed: set -o pipefail
printf '%s\n' '--- exact action and helper lines ---'
nl -ba crates/bitty-terminal/src/chrome_keys.rs | sed -n '1110,1175p;1372,1435p;1588,1620p'
printf '%s\n' '--- exact focus implementation ---'
rg -n -F -- 'pub fn set_focus' crates/bitty-runtime/src
focus_line=$(rg -n -F -- 'pub fn set_focus' crates/bitty-runtime/src | head -1 | cut -d: -f2)
if test -n "$focus_line"; then
start=$((focus_line-18)); end=$((focus_line+55))
nl -ba crates/bitty-runtime/src/runtime/layout_focus.rs | sed -n "${start},${end}p"
fi
printf '%s\n' '--- NewPanel contract and fresh-workspace documentation ---'
nl -ba crates/bitty-runtime/src/config.rs | sed -n '1045,1075p'
nl -ba crates/bitty-runtime/src/runtime/workspaces.rs | sed -n '25,48p;1835,1870p;3475,3510p'
printf '%s\n' '--- default keymap support and sibling references ---'
rg -n -i --glob '*.rs' --glob '*.toml' --glob '*.md' -e 'new_panel' -e 'new_split' -e '2222' -e '2223' -e 'CTX-1336' -e 'CTX-.*new.panel' . 2>/dev/null || true
printf '%s\n' '--- relevant history refs, if present locally ---'
git log --all --oneline --decorate -- crates/bitty-terminal/src/chrome_keys.rs | head -25Repository: bitty-terminal/bitty Length of output: 37937 🏁 Script executed: set -o pipefail
printf '%s\n' '--- keymap action contract and defaults ---'
nl -ba crates/bitty-config/src/keymap.rs | sed -n '645,675p;1658,1675p'
printf '%s\n' '--- set_focus implementation ---'
nl -ba crates/bitty-runtime/src/runtime/layout_focus.rs | sed -n '2210,2265p'
printf '%s\n' '--- split helper ---'
nl -ba crates/bitty-terminal/src/chrome_keys.rs | sed -n '560,610p'
printf '%s\n' '--- history after reviewed commit, without pipe truncation ---'
git log --all --oneline --decorate -- crates/bitty-terminal/src/chrome_keys.rs > /tmp/bitty_chrome_history.txt
sed -n '1,20p' /tmp/bitty_chrome_history.txtRepository: bitty-terminal/bitty Length of output: 10400 Handle the empty tiled layout in panel creation. After 🤖 Prompt for AI Agents |
||
| eprintln!( | ||
| "bitty: keymap close_view -> float base drained, leafs={} focused={:?}", | ||
| self.runtime.leaf_count(), | ||
| self.runtime.focused_view() | ||
| ); | ||
| } else if self.runtime.is_primary_view(&focused) { | ||
| if self.runtime.close_primary_session() { | ||
| eprintln!( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2396,8 +2396,36 @@ impl TerminalApp { | |
| if self.runtime.floated_views().contains(&view) | ||
| || self.runtime.pinned_views().contains(&view) | ||
| { | ||
| let was_float = self.runtime.floated_views().contains(&view); | ||
| self.restore_zoom(); | ||
| self.runtime.discard_floating_view(view); | ||
| // CTX-1336 (#2221): the exited float may have been the | ||
| // active workspace's last live view (its tiled base | ||
| // already closed). Leaving the slot with no layout | ||
| // leaves and no floats strands a blank, dead workspace | ||
| // until the user switches away or closes it. Prune it | ||
| // exactly like the CTX-1297/close_view paths; a single | ||
| // remaining workspace keeps the window-close gesture | ||
| // handled by the effective-window-count gate above. | ||
| if was_float | ||
| && self.runtime.layout().leaf_count() == 0 | ||
| && self.runtime.floated_views().is_empty() | ||
| && self.runtime.workspace_count() > 1 | ||
| { | ||
| let workspace = self.runtime.active_workspace_index(); | ||
| if self.runtime.workspace_close_at(workspace).is_ok() { | ||
| crate::logging::info(|| { | ||
| format!( | ||
| "bitty: pane shell {view:?} exited — empty workspace pruned" | ||
| ) | ||
| }); | ||
| } else { | ||
| crate::logging::warn(|| { | ||
| "bitty: could not prune empty workspace after float exit" | ||
| .to_string() | ||
| }); | ||
| } | ||
| } | ||
|
Comment on lines
+2399
to
+2428
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: sed -n '2390,2450p' crates/bitty-terminal/src/terminal_app.rs
sed -n '690,760p' crates/bitty-runtime/src/runtime/workspaces.rs
rg -n 'remove_stashed_view|empty workspace|reap_exited_shells' crates/bitty-terminal/src/tests.rs crates/bitty-runtime/src/runtime/workspaces.rsRepository: bitty-terminal/bitty Length of output: 9839 🏁 Script executed: set -o pipefail
printf '%s\n' '--- reaper control flow ---'
nl -ba crates/bitty-terminal/src/terminal_app.rs | sed -n '2280,2495p'
printf '%s\n' '--- stashed removal implementation ---'
nl -ba crates/bitty-runtime/src/runtime/workspaces.rs | sed -n '675,790p'
printf '%s\n' '--- stashed removal call sites ---'
rg -n -F -- 'remove_stashed_view(' crates
printf '%s\n' '--- workspace stash/load implementation ---'
nl -ba crates/bitty-runtime/src/runtime/workspaces.rs | sed -n '1010,1085p;1225,1285p'
printf '%s\n' '--- post-removal helper references ---'
rg -n -F -- 'after_stashed_removal' crates/bitty-runtime/src/runtime/workspaces.rsRepository: bitty-terminal/bitty Length of output: 27072 🏁 Script executed: set -o pipefail
printf '%s\n' '--- pane session enumeration and related ownership ---'
rg -n -F -- 'pub fn pane_session_ids' crates/bitty-runtime crates/bitty-terminal
rg -n -F -- 'pane_session_ids(' crates/bitty-runtime crates/bitty-terminal
printf '%s\n' '--- pane_session_ids definition ---'
line=$(rg -n -F -- 'pub fn pane_session_ids' crates/bitty-runtime | head -n1 | cut -d: -f1)
if test -n "$line"; then start=$((line-12)); end=$((line+35)); nl -ba crates/bitty-runtime/src/runtime/workspaces.rs | sed -n "${start},${end}p"; fi
printf '%s\n' '--- workspace_close_at definition and callers ---'
rg -n -F -- 'workspace_close_at' crates/bitty-runtime/src/runtime/workspaces.rs crates/bitty-terminal/src/terminal_app.rs
line=$(rg -n -F -- 'pub fn workspace_close_at' crates/bitty-runtime/src/runtime/workspaces.rs | head -n1 | cut -d: -f1)
if test -n "$line"; then start=$((line-10)); end=$((line+105)); nl -ba crates/bitty-runtime/src/runtime/workspaces.rs | sed -n "${start},${end}p"; fiRepository: bitty-terminal/bitty Length of output: 9901 🏁 Script executed: set -o pipefail
printf '%s\n' '--- pane_session_ids definition ---'
nl -ba crates/bitty-runtime/src/runtime/panes.rs | sed -n '725,765p'
printf '%s\n' '--- remove_workspace and adjacent workspace lifecycle ---'
rg -n -F -- 'fn remove_workspace' crates/bitty-runtime/src/runtime/workspaces.rs
line=$(rg -n -F -- 'fn remove_workspace' crates/bitty-runtime/src/runtime/workspaces.rs | head -n1 | cut -d: -f2)
if test -n "$line"; then start=$((line-20)); end=$((line+115)); nl -ba crates/bitty-runtime/src/runtime/workspaces.rs | sed -n "${start},${end}p"; fiRepository: bitty-terminal/bitty Length of output: 2202 🏁 Script executed: set -o pipefail
printf '%s\n' '--- slot type and empty-state APIs ---'
rg -n -F -e 'struct WorkspaceSlot' -e 'impl WorkspaceSlot' -e 'pub fn workspace_' crates/bitty-runtime/src/runtime/workspaces.rs | head -80Repository: bitty-terminal/bitty Length of output: 2349 Prune the owning workspace after an inactive float exits. When the user switches workspaces before the float shell exits, Make stashed-float removal identify its owning workspace, then apply the same empty-workspace pruning to that workspace. Do not use 🤖 Prompt for AI Agents |
||
| self.push_pending_exit(view.0, status.code()); | ||
| closed_any = true; | ||
| continue; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
🔎 Supported by static analysis
🏁 Script executed:
Repository: bitty-terminal/bitty
Length of output: 7666
🏁 Script executed:
Repository: bitty-terminal/bitty
Length of output: 42822
🏁 Script executed:
Repository: bitty-terminal/bitty
Length of output: 28487
🏁 Script executed:
Repository: bitty-terminal/bitty
Length of output: 40833
🏁 Script executed:
Repository: bitty-terminal/bitty
Length of output: 10499
Handle float-only workspaces during session persistence.
When the last tiled leaf is removed,
capture_session_snapshotrecords an empty layout while storing the float separately.validate_snapshotrejects that workspace withSessionError::Corrupt("empty workspace"). Thereforesave_session_on_exitonly warns and does not persist the session.Define how a float-only workspace is represented during save and restore before relying on this teardown state.
🤖 Prompt for AI Agents