From 058ce49d3b26c1cd0fea3dbe0d1c18c15e1c01e4 Mon Sep 17 00:00:00 2001 From: megaproxy Date: Fri, 22 May 2026 16:33:21 +0100 Subject: [PATCH] Force gutter-drag resize via direct DOM (same workaround) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dragging the splitter set node.ratio in the Svelte $state tree correctly (used by save-restore), but the template binding style=\"flex: {node.ratio}\" on each .side div didn't re-evaluate when ratio changed — same prop-reactivity wall we hit with the active border and broadcast color. The gutter would drag invisibly: internal state moved, panes stayed at their original ratio. Workaround: SplitNode's onPointerMove now ALSO writes the flex style directly to the two .side elements via DOM. Svelte still owns node.ratio for persistence/serialization, but the visual is owned by the imperative DOM write. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/App.svelte | 24 ++++++++++++++++++++++++ src/lib/layout/SplitNode.svelte | 7 +++++++ 2 files changed, 31 insertions(+) diff --git a/src/App.svelte b/src/App.svelte index 03af48d..7a5c46e 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -5,6 +5,7 @@ saveWorkspace, loadWorkspace, writeToPane, + killPane, } from "./ipc"; import Pane from "./lib/layout/Pane.svelte"; import Notifications from "./components/Notifications.svelte"; @@ -65,6 +66,29 @@ } function handleClose(leafId: NodeId) { + // Kill the PTY explicitly — Svelte's onDestroy on LeafPane won't fire + // because Svelte isn't unmounting the component (same reactivity gap as + // the active/broadcast bugs). + const paneId = orch.paneIdByLeaf.get(leafId); + if (paneId != null) { + void killPane(paneId).catch((e) => console.warn("killPane failed:", e)); + orch.paneIdByLeaf.delete(leafId); + } + + // Brute-force hide the closed pane's flex side + the adjacent gutter so + // the sibling pane visually fills the freed space. + const leafEl = document.querySelector(`[data-leaf-id="${leafId}"]`); + const sideEl = leafEl?.closest(".side") as HTMLElement | null; + const splitEl = sideEl?.parentElement; + if (sideEl && splitEl) { + sideEl.style.display = "none"; + Array.from(splitEl.children).forEach((c) => { + const child = c as HTMLElement; + if (child.classList.contains("gutter")) child.style.display = "none"; + }); + } + + // Update tree state (used by broadcast routing, palette, persistence). const next = closeLeaf(tree, leafId); tree = next ?? newLeaf({ distro: defaultDistro }); clearActiveIf(leafId); diff --git a/src/lib/layout/SplitNode.svelte b/src/lib/layout/SplitNode.svelte index 83a9cff..0f5bbe6 100644 --- a/src/lib/layout/SplitNode.svelte +++ b/src/lib/layout/SplitNode.svelte @@ -28,6 +28,13 @@ if (size <= 0) return; const r = Math.max(0.05, Math.min(0.95, pos / size)); node.ratio = r; + + // Brute-force DOM: Svelte's `style="flex: {node.ratio}"` template binding + // doesn't propagate ratio changes in this app. Update the .side flex + // styles directly so the drag actually moves the gutter visually. + const sides = containerEl.querySelectorAll(":scope > .side"); + if (sides[0]) (sides[0] as HTMLElement).style.flex = String(r); + if (sides[1]) (sides[1] as HTMLElement).style.flex = String(1 - r); } function onPointerUp(e: PointerEvent) {