sinelaw/fresh · critical
active window must have a populated split layout
Error message
active window must have a populated split layout
What it means
`focus_split` asserts that the active window has a split layout via `splits().expect(...)`. If the active window's split tree is absent or empty, the internal invariant is broken and the process panics. The editor assumes a newly focused window always has a populated split root.
Solutions
- Ensure the window's split layout is initialized (a root split exists) before focus handling runs
- Guard focus handling: skip when splits() returns None and log instead of panicking
- Reproduce with the failing state and fix where the split tree was dropped/not created
- Convert the expect to a graceful early-return in input-handling code paths
Example fix
// before
let in_main_tree = self.buffers.splits().expect("active window must have a populated split layout").0.root().leaf_split_ids().contains(&split_id);
// after
let Some(splits) = self.buffers.splits() else { return; };
let in_main_tree = splits.0.root().leaf_split_ids().contains(&split_id); Defensive patterns
Strategy: type-guard
Validate before calling
if self.buffers.splits().is_none() || self.buffers.splits().unwrap().0.root().leaf_split_ids().is_empty() {
return; // window not ready for split focus
} Type guard
fn has_split_layout(buffers: &Buffers) -> bool {
buffers.splits().map(|(tree, _)| !tree.root().leaf_split_ids().is_empty()).unwrap_or(false)
} Prevention
- Initialize the split tree at window creation, before input handling
- Never allow the active window to have an unpopulated layout; treat it as a lifecycle bug
- Add debug assertions/tests that splits() is Some in focus paths
- Convert expects in input handlers to logged early-returns
When it happens
Trigger: Calling `focus_split` when the active window has no populated split layout — e.g. no buffers open yet, or window state created without initializing the split tree, then routing a click/keystroke that focuses `split_id`.
Common situations: Focusing a split before the first buffer opens; a regression in window teardown leaving splits() None/empty; automated tests constructing a window without populating splits.
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
AI-assisted analysis of sinelaw/fresh@67894ca546 (2026-09-13).
Data as JSON: /api/errors/5872acfe9b9e0fc9.
Report an issue: GitHub.
Appendix: source
Thrown at crates/fresh-editor/src/app/active_focus.rs:269
let previous_buffer = self.active_buffer(); // Get BEFORE changing split
let split_changed = previous_split != split_id;
// Preview is anchored to the split it was opened in. Moving focus to
// a different split commits the preview — walking away is commitment.
if split_changed {
self.promote_preview_if_not_in_split(split_id);
}
// If `split_id` is not in the main split tree, it must be an inner
// leaf of a Grouped subtree stashed in `grouped_subtrees`. For those
// we don't change `split_manager.active_split` (the group's host
// split remains active). Instead, find the host split and update
// its `focused_group_leaf` marker so `active_buffer()` routes to
// the clicked inner panel buffer.
let in_main_tree = self
.buffers
.splits()
.expect("active window must have a populated split layout")
.0
.root()
.leaf_split_ids()
.contains(&split_id);
if !in_main_tree {
// Find which group contains this inner leaf.
let group_leaf_id = self
.grouped_subtrees
.iter()
.find(|(_, node)| {
if let crate::view::split::SplitNode::Grouped { layout, .. } = node {
layout.find(split_id.into()).is_some()
} else {
false
}
})
.map(|(group_leaf_id, _)| *group_leaf_id);
let host_split = group_leaf_id.and_then(|group_leaf_id| {View on GitHub (pinned to 67894ca546)