Skip to content

Commit cd78b76

Browse files
claudesinelaw
authored andcommitted
fix(tabs): extracted co-tenant opens with only the moved tab
A freshly created co-tenant window is born with a throwaway `[No Name]` scratch seed so it is renderable the instant `window_created` fires (`build_fresh_layout_if_needed`). The extract flow then *added* the moved buffer alongside that seed, so a single-file extraction opened the new workspace showing the extracted file **and** a stray empty `[No Name]` tab. Drop the birth seed (via new `discard_fresh_window_seed`, guarded to only ever touch a lone unnamed/unmodified buffer) right after creating the co-tenant and before the move, so `move_buffer_membership_to_window` re-seeds the split rooted at the extracted buffer — leaving it as the window's sole tab. Applied to both the file and terminal extract paths. Regression test `extract_tab_new_workspace_has_only_the_moved_tab` asserts the co-tenant shows the moved file, contains no `[No Name]`, and owns exactly one buffer. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GQYut8xrWsYyQffc4D84dz
1 parent e6b48da commit cd78b76

2 files changed

Lines changed: 61 additions & 0 deletions

File tree

crates/fresh-editor/src/app/window_actions.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,38 @@ impl crate::app::Editor {
169169
id
170170
}
171171

172+
/// Drop the throwaway `[No Name]` seed a freshly created window is born
173+
/// with (via [`Self::build_fresh_layout_if_needed`], so it is renderable
174+
/// the instant `window_created` fires). Extraction is about to move the
175+
/// real tab in as the window's sole content, so clearing the seed first
176+
/// lets [`Self::move_buffer_membership_to_window`] re-seed the split
177+
/// rooted at the extracted buffer — otherwise the co-tenant opens showing
178+
/// the extracted tab *and* a stray empty `[No Name]`.
179+
///
180+
/// Guarded to only ever touch that birth seed: exactly one buffer, unnamed
181+
/// and unmodified. Anything else is real content and is left untouched.
182+
/// Safe to call only before any render of `target` (the extract flow runs
183+
/// synchronously, so no render intervenes).
184+
fn discard_fresh_window_seed(&mut self, target: WindowId) {
185+
let Some(w) = self.windows.get_mut(&target) else {
186+
return;
187+
};
188+
let is_birth_seed = w.buffers.len() == 1
189+
&& w.buffers
190+
.iter()
191+
.next()
192+
.is_some_and(|(_, s)| s.buffer.file_path().is_none() && !s.buffer.is_modified());
193+
if !is_birth_seed {
194+
return;
195+
}
196+
for id in w.buffers.ids() {
197+
w.buffers.remove(&id);
198+
w.buffer_metadata.remove(&id);
199+
w.event_logs.remove(&id);
200+
}
201+
w.buffers.clear_splits();
202+
}
203+
172204
/// Create a new window rooted at `root` under an explicit `authority`,
173205
/// seeded with an empty scratch buffer + minimal split layout (so it is
174206
/// renderable immediately) and announced via `window_created`.
@@ -904,6 +936,9 @@ impl crate::app::Editor {
904936
self.retarget_leaves_off_buffer(buffer_id);
905937

906938
let target = self.create_co_tenant_window(root, self.active_window);
939+
// Drop the co-tenant's birth `[No Name]` seed so the extracted tab is
940+
// its only tab, not a sibling of a stray empty buffer.
941+
self.discard_fresh_window_seed(target);
907942
self.move_buffer_membership_to_window(buffer_id, target);
908943

909944
let target_label = self
@@ -956,6 +991,9 @@ impl crate::app::Editor {
956991
self.retarget_leaves_off_buffer(buffer_id);
957992

958993
let target = self.create_co_tenant_window(root, self.active_window);
994+
// Drop the co-tenant's birth `[No Name]` seed so the moved terminal is
995+
// its only tab, not a sibling of a stray empty buffer.
996+
self.discard_fresh_window_seed(target);
959997

960998
self.move_terminal_machinery_to_window(buffer_id, terminal_id, target);
961999
self.move_buffer_membership_to_window(buffer_id, target);

crates/fresh-editor/tests/e2e/extract_tab_to_workspace.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,29 @@ fn extract_tab_via_command_palette_moves_buffer_to_new_workspace() {
104104
);
105105
}
106106

107+
#[test]
108+
fn extract_tab_new_workspace_has_only_the_moved_tab() {
109+
let mut harness = harness_with_subproject_file();
110+
111+
run_command_palette(&mut harness, "Extract Tab to New Workspace");
112+
harness.assert_screen_contains("Extracted notes.txt into workspace project_root (2)");
113+
114+
// The co-tenant must open showing *only* the extracted tab — not the
115+
// throwaway `[No Name]` seed the window is born with for renderability.
116+
harness.assert_screen_contains("notes.txt");
117+
let screen = harness.screen_to_string();
118+
assert!(
119+
!screen.contains("[No Name]"),
120+
"the extracted co-tenant should not carry a stray '[No Name]' seed tab, \
121+
got screen:\n{screen}"
122+
);
123+
assert_eq!(
124+
harness.editor().active_window().buffers.len(),
125+
1,
126+
"the co-tenant should own exactly the moved buffer"
127+
);
128+
}
129+
107130
#[test]
108131
fn extract_tab_preserves_unsaved_edits_and_undo_history() {
109132
let mut harness = harness_with_subproject_file();

0 commit comments

Comments
 (0)