sinelaw/fresh · error
has_other_tab
Error message
has_other_tab
What it means
Panic from `.expect("has_other_tab")` in `close_tab_in_split` (crates/fresh-editor/src/app/buffer_close.rs:765). After removing the closing target from `targets`, the code assumes at least one other tab remains in the split to activate; when the closed tab was the only distinct target, `find` returns None and the assert fires.
Solutions
- Check `targets.len() > 1` / existence of a non-closing target before entering the replacement-selection branch and route single-tab splits to the split-close path
- Guard with `if let Some(next) = ... else { return }` and close the split instead of panicking
- Ensure the earlier `handle_close_split` branch catches all single-target splits so this line is only reached with leftovers
- Add a unit test: close the sole tab of a split
Example fix
// before
*targets.iter().find(|t| **t != closing).expect("has_other_tab")
// after
match targets.iter().find(|t| **t != closing) {
Some(t) => *t,
None => return self.handle_close_split(split_id.into()),
} Defensive patterns
Strategy: type-guard
Validate before calling
let other = targets.iter().find(|t| *t != closing);
if other.is_none() { /* route to handle_close_split instead of continuing */ } Type guard
fn replacement_target<'a>(targets: &'a [TabTarget], closing: &TabTarget) -> Option<&'a TabTarget> {
targets.iter().find(|t| *t != closing)
} Try / catch
match targets.iter().find(|t| *t != closing) { Some(t) => activate(t), None => close_split(split_id) } Prevention
- Always handle the single-tab split case explicitly before selecting a replacement
- Deduplicate TabTarget entries before index math
- Unit-test closing the only tab in a split
- Never .expect on iterator .find over user-mutable collections
When it happens
Trigger: Closing a tab whose split contains only that one target while the earlier `closing_idx == 0 && targets.len() == 1` branch is reached (i.e. `targets` filtered to `!= closing` is empty).
Common situations: Closing the last remaining tab in a split where the tab-closing logic already removed the split (ordering mismatch with the `split_tabs.len() <= 1` branch); duplicated TabTarget entries that make `position`/dedupe logic select an unexpected index; calling close on an already-closing buffer.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- active window present
- active window present
- active window must have a populated split layout
- active window present
- active window present
AI-assisted analysis of sinelaw/fresh@67894ca546 (2026-09-13).
Data as JSON: /api/errors/400f68024aff37db.
Report an issue: GitHub.
Appendix: source
Thrown at crates/fresh-editor/src/app/buffer_close.rs:765
if !has_other_tab {
// This is genuinely the only tab in this split — close it.
self.handle_close_split(split_id.into());
self.sync_terminal_mode_to_active_buffer();
return true;
}
// Pick the tab to activate after removal: the one before the
// closed tab (or the next one if we closed the first). This
// mirrors the previous buffer-only behaviour but can also land
// on a remaining group tab.
let replacement = if closing_idx > 0 {
targets[closing_idx - 1]
} else {
// First remaining target after the closed one.
*targets
.iter()
.find(|t| **t != closing)
.expect("has_other_tab")
};
// Activate the replacement tab and drop the closed one. The buffer
// case must move the split tree AND the `SplitViewState.active_buffer`
// together: routing it through `set_pane_buffer` (not the tree-only
// `set_split_buffer`) is the fix for the cursor desync — updating
// only the tree stranded the view-state on the just-closed buffer,
// so the cursor and render read its zeroed view-state while edits
// applied to the tree's (different) buffer.
match replacement {
TabTarget::Buffer(replacement_buffer) => {
self.active_window_mut()
.set_pane_buffer(split_id, replacement_buffer);
// The replacement is active now, so removing the closed
// buffer also frees its keyed view-state (`remove_buffer`
// refuses to drop the state of whatever is still active).
if let Some(view_state) = self
.windowsView on GitHub (pinned to 67894ca546)