fix(desktop): keep tab bar visible when toggling bottom panel panes

When terminal and logs share a zone, toggling one off no longer folds the
entire zone — it switches to the still-open sibling so the tab bar stays
accessible. When the zone does collapse with ≥2 panes, the horizontal tab
bar remains visible (instead of degrading to a vertical rail) so the user
can switch between terminal/logs without expanding first.

- store.ts: add paneOpenGetters registry; setPaneCollapsed checks for an
  open sibling before minimizing the zone
- controller.tsx: bindPaneCollapse registers an open-state getter
- tree-group.tsx: verticalCollapse only for lone panes; ≥2 keeps the strip
This commit is contained in:
ethernet 2026-07-16 14:30:08 -04:00
parent bd37ff9138
commit 0e704e9220
3 changed files with 29 additions and 2 deletions

View file

@ -21,6 +21,7 @@ import {
registerLayoutResetHandler,
registerPaneCloser,
registerPaneOpener,
registerPaneOpenGetter,
resetLayoutTree,
revealTreePane,
setPaneCollapsed,
@ -468,6 +469,7 @@ function bindPaneCollapse(
$open.listen(isOpen => setPaneCollapsed(paneId, !isOpen))
registerPaneCloser(paneId, close)
registerPaneOpener(paneId, open)
registerPaneOpenGetter(paneId, () => $open.get())
}
// SIDES have one source of truth: the TREE. The legacy $panesFlipped flag is

View file

@ -204,7 +204,13 @@ export function TreeGroup({
// empty column, so the minimized form is a narrow vertical rail instead
// (tabs reading top-to-bottom). In a column (stacked zones) the horizontal
// header IS the collapsed form, exactly as before.
const verticalCollapse = Boolean(node.minimized) && parentAxis === 'row' && !isEmpty
//
// EXCEPTION: when the zone has ≥2 shown panes, keep the horizontal tab bar
// even when minimized — the user can still switch between terminal/logs
// without expanding the zone first. The vertical rail is only for a lone
// pane where the strip adds no value.
const verticalCollapse =
Boolean(node.minimized) && parentAxis === 'row' && !isEmpty && shown.length <= 1
const headerVisible = !isEmpty && !verticalCollapse && (Boolean(node.minimized) || !headerHidden)
// Drag handles preventDefault pointerdown (no native dblclick), so the

View file

@ -164,6 +164,7 @@ function setDismissed(paneId: string, dismissed: boolean) {
const paneClosers: Record<string, () => void> = {}
const paneOpeners: Record<string, () => void> = {}
const paneOpenGetters: Record<string, () => boolean> = {}
/** Route a pane's Close through the app store that owns its visibility. */
export function registerPaneCloser(paneId: string, close: () => void) {
@ -181,6 +182,13 @@ export function registerPaneOpener(paneId: string, open: () => void) {
paneOpeners[paneId] = open
}
/** Register a getter that reads a tool pane's open/closed state from its
* owning store so `setPaneCollapsed` can check if a sibling in the same
* zone is still open before folding the whole zone. */
export function registerPaneOpenGetter(paneId: string, getOpen: () => boolean) {
paneOpenGetters[paneId] = getOpen
}
// TOOL PANELS (terminal, logs, …): their toggle COLLAPSES the zone to a rail
// (tab stays) instead of hiding it, and the tab's ✕ REMOVES it (vs a session
// tile, whose ✕ closes the session). Membership tells the renderer which
@ -1067,7 +1075,18 @@ export function setPaneCollapsed(paneId: string, collapsed: boolean) {
activateTreePane(group.id, group.panes[at - 1] ?? group.panes[at + 1])
} else {
toggleTreeGroupMinimized(group.id, true) // pure tool zone folds as a unit
// Pure tool zone: if another tool pane in this zone is still open,
// switch to it instead of folding the whole zone — so the tab bar
// stays visible and the user can switch between terminal/logs freely.
const openSibling = group.panes.find(
id => id !== paneId && isCollapsePane(id) && paneOpenGetters[id]?.()
)
if (openSibling) {
activateTreePane(group.id, openSibling)
} else {
toggleTreeGroupMinimized(group.id, true) // no open sibling — fold as a unit
}
}
} else if (!collapsed) {
revealTreePane(paneId)