aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-13 13:35:38 +0200
committeruser <user@clank>2026-08-13 13:35:38 +0200
commited892dbdae90b738d8a1cdef7bc31d6992ea399b (patch)
tree0a43d4933d25c111215adcd5ba343d9d2a7dbac1
parentdocs: record the responsive stacking bug with a deterministic repro (diff)
downloadsl0ppi-ed892dbdae90b738d8a1cdef7bc31d6992ea399b.tar.gz
sl0ppi-ed892dbdae90b738d8a1cdef7bc31d6992ea399b.tar.xz
sl0ppi-ed892dbdae90b738d8a1cdef7bc31d6992ea399b.zip
sl0p: resolve responsive layouts against hidden panes too
A pane created after a responsive collapse/expand cycle was absorbed into a stack, even at full width. Reported as "a new pane starts collapsed even though the terminal is big enough", and it appeared to fix itself on resize. The expand was never really happening. `SwapLayouts::tiled_layout_at` checked whether a candidate layout fits by positioning it against `tiled_panes.visible_panes_count()` -- and a stack hides every member but one. So while collapsed, the check ran with a pane count of 1, and a layout with no room for the hidden panes passed as feasible. The tab reported `sl0p:responsive-expand` while every pane stayed in the single rect it had while collapsed; the next pane added simply joined that stack. Feasibility is now checked against every tiled pane, hidden or not, which is the count the layout actually has to accommodate. Only the responsive collapse/expand resolves layouts through this function -- ordinary swap-layout cycling has its own body in `swap_tiled_panes` -- so the change is contained to the path that was wrong. Verified on the test box (responsive_min_cols 90) with a scripted pty that resizes mid-session, geometry read back from `list-panes --json`: before narrow(80) 2 panes -> one rect, both 80x23 (correct collapse) widen to 160 -> STILL one rect, 160x44 (expand did nothing) add a pane -> new pane joins the stack after narrow(80) 2 panes -> one rect (collapse unchanged) widen to 160 -> x=0 and x=80, side by side (really expanded) add three panes -> 2+1, then 2x2, then 3+2 grids, no shared rects wide from the start -> unaffected narrow again (70) -> collapses, as before No unit test ships with this, deliberately. Three attempts could not make one discriminate: the tab harness does no rendering, so nothing populates `panes_to_hide` and both counts are equal; hiding panes by hand does not help because the harness's expand takes the BASE path; and asserting on the resolver directly bypasses the count, since `tiled_layout_at` uses it only for the feasibility check and returns the raw layout. A test that passes with the fix reverted is worse than none, so the scripted live reproduction above is the verification, and it is written down in docs/roadmap.md to be re-run. zellij-server 1502 pass, zellij-utils 473 pass.
-rw-r--r--VERSION2
-rw-r--r--sl0ppij/zellij-server/src/panes/tiled_panes/mod.rs10
-rw-r--r--sl0ppij/zellij-server/src/tab/swap_layouts.rs14
3 files changed, 24 insertions, 2 deletions
diff --git a/VERSION b/VERSION
index 845639e..9faa1b7 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.1.4
+0.1.5
diff --git a/sl0ppij/zellij-server/src/panes/tiled_panes/mod.rs b/sl0ppij/zellij-server/src/panes/tiled_panes/mod.rs
index 7c702bf..1072ca2 100644
--- a/sl0ppij/zellij-server/src/panes/tiled_panes/mod.rs
+++ b/sl0ppij/zellij-server/src/panes/tiled_panes/mod.rs
@@ -3083,6 +3083,16 @@ impl TiledPanes {
pub fn visible_panes_count(&self) -> usize {
self.panes.len().saturating_sub(self.panes_to_hide.len())
}
+ /// Every tiled pane, including the ones currently hidden.
+ ///
+ /// sl0p fork: `visible_panes_count` deliberately excludes hidden panes,
+ /// which is right for rendering and wrong for deciding how many panes a
+ /// layout has to accommodate. A stack hides all of its members but one, so
+ /// a layout resolved against the visible count is built for too few panes --
+ /// see `SwapLayouts::tiled_layout_at`.
+ pub fn all_panes_count(&self) -> usize {
+ self.panes.len()
+ }
pub fn add_to_hidden_panels(&mut self, pid: PaneId) {
self.panes_to_hide.insert(pid);
}
diff --git a/sl0ppij/zellij-server/src/tab/swap_layouts.rs b/sl0ppij/zellij-server/src/tab/swap_layouts.rs
index 016ba5f..3b8cb3f 100644
--- a/sl0ppij/zellij-server/src/tab/swap_layouts.rs
+++ b/sl0ppij/zellij-server/src/tab/swap_layouts.rs
@@ -198,7 +198,19 @@ impl SwapLayouts {
for (constraint, layout) in swap_layout.0.iter() {
if self.state_fits_tiled_panes_constraint(constraint, tiled_panes) {
let display_area = self.display_area.borrow();
- let pane_count = tiled_panes.visible_panes_count();
+ // sl0p fork: resolve against EVERY pane, not just the visible
+ // ones. Expanding out of a stack is exactly where that differs:
+ // a stack hides all its members but one, so
+ // `visible_panes_count()` undercounts, `position_panes_in_space`
+ // takes its `else` branch and never inserts the extra children,
+ // and the "expand" layout resolves to a shape with no room for
+ // the hidden panes. The tab then reports it expanded while every
+ // pane stays in the one rect it had while collapsed.
+ //
+ // Only the responsive collapse/expand resolves layouts through
+ // here; ordinary swap-layout cycling has its own body in
+ // `swap_tiled_panes` and is untouched.
+ let pane_count = tiled_panes.all_panes_count();
let display_area = PaneGeom::from(&*display_area);
if layout
.position_panes_in_space(&display_area, Some(pane_count), false, true)