From 40ce27251d5831158926eb8c168595fe31930833 Mon Sep 17 00:00:00 2001 From: megaproxy Date: Fri, 22 May 2026 17:04:00 +0100 Subject: [PATCH] Close: bubble-up DOM hide + key-bump on full collapse MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two improvements to the close-pane workaround: 1. When both children of a parent split are now hidden, the .side hide bubbles up to the parent split's own .side and hides that too. Needed for deeply-nested splits where closing leaves at the bottom should propagate the visual collapse upward. 2. When closeLeaf returns null (the user closed the last remaining leaf), force a full Pane remount via {#key renderKey} bump. The DOM-hide approach can't simulate mounting a fresh tree node, so this is the one place where we take the cost of a full unmount + remount. Only fires when the entire tree resets — not on intermediate closes. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/App.svelte | 47 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/src/App.svelte b/src/App.svelte index 4836b9b..7d5306c 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -56,6 +56,11 @@ if (activeLeafId === id) activeLeafId = null; } + // Bumped to force a full Pane unmount+remount when the tree's structure + // changes in a way the DOM-hide workaround can't simulate (specifically, + // when ALL panes have been closed and we need to render a fresh leaf). + let renderKey = $state(0); + // ---- tree mutation handlers (closures over tree $state) ----------------- function handleSplit(leafId: NodeId, orientation: Orientation) { const parent = findLeaf(tree, leafId); @@ -73,21 +78,45 @@ void killPane(paneId).catch((e) => console.warn("killPane failed:", e)); orch.paneIdByLeaf.delete(leafId); } - // Hide the closed pane's flex container and the adjacent gutter so the - // sibling pane visually fills the freed space. + + // DOM-hide the .side wrapping this leaf and the adjacent gutter so the + // sibling pane visually fills. If both sides of the parent split are + // now hidden, bubble up and hide that whole split too (recursive + // collapse — needed for nested closes). const leafEl = document.querySelector(`[data-leaf-id="${leafId}"]`); - const sideEl = leafEl?.closest(".side") as HTMLElement | null; - const splitEl = sideEl?.parentElement; - if (sideEl && splitEl) { + let sideEl = leafEl?.closest(".side") as HTMLElement | null; + while (sideEl) { sideEl.style.display = "none"; + const splitEl = sideEl.parentElement; + if (!splitEl) break; + // Hide the gutter in this split (only one). Array.from(splitEl.children).forEach((c) => { const child = c as HTMLElement; if (child.classList.contains("gutter")) child.style.display = "none"; }); + // If a sibling side is still visible, flex will auto-fill; we're done. + const visibleSiblings = Array.from(splitEl.children).filter((c) => { + const child = c as HTMLElement; + return ( + child.classList.contains("side") && + child.style.display !== "none" + ); + }); + if (visibleSiblings.length > 0) break; + // Otherwise, this whole split is empty — climb up and hide its parent side. + sideEl = splitEl.closest(".side") as HTMLElement | null; } - // Update tree state (used by broadcast routing, palette, persistence). + + // Update tree state for persistence / palette / broadcast routing. const next = closeLeaf(tree, leafId); - tree = next ?? newLeaf({ distro: defaultDistro }); + if (next === null) { + // Tree fully collapsed. The DOM-hide workaround can't simulate + // mounting a brand-new leaf, so force a clean remount via key bump. + tree = newLeaf({ distro: defaultDistro }); + renderKey += 1; + } else { + tree = next; + } clearActiveIf(leafId); } @@ -332,7 +361,9 @@
{#if ready} - + {#key renderKey} + + {/key} {/if}