Skip to content

Toggle any pane wherever you put it, and keep the header hidden - #75848

Merged
OutThisLife merged 6 commits into
mainfrom
bb/toggle-terminal-persist
Aug 1, 2026
Merged

Toggle any pane wherever you put it, and keep the header hidden#75848
OutThisLife merged 6 commits into
mainfrom
bb/toggle-terminal-persist

Conversation

@OutThisLife

@OutThisLife OutThisLife commented Aug 1, 2026

Copy link
Copy Markdown
Collaborator

Dragging the terminal to the bottom stacks logs into its zone, and every tool-panel toggle broke in that stack — ⌃`, the statusbar button, ⌘J's terminal fallback. A lone terminal never reproduced it, which is why it read as randomly broken: it worked again only after closing the terminal from its own tab, because that broke the stack up.

Auditing the rest of the toggles found the same divergence on ⌘G and ⌘J, so this fixes the class rather than the one key.

The toggles asked a boolean, not the tree

!$terminalTakeover.get() flips a value with no idea what's on screen. Once anything else moved the pane — stacked behind a sibling tab, minimized from the zone menu, closed with ⌘W — the store said "open" while nothing was visible, and the press spent itself re-asserting a value it already held.

A probe confirmed it was never terminal-specific. With review stacked behind files in the right column, or either pane inside a minimized zone, ⌘G and ⌘J were dead in exactly the same way. isPaneVisible / togglePaneVisible now back every toggle. Close still routes through closeTreePane, so each pane keeps its own semantics: a tool panel collapses to its rail, files and review close through their store, anything else is dismissed.

setTreePaneHidden keeps its quiet path — a reactive unhide (a cwd arriving) must not front or un-minimize over what the user is looking at. Only user intent takes the reveal path.

files and review had a closer and no opener

So their booleans went stale the moment anything but the toggle revealed them. bindPaneVisibility moves into the tree store beside bindToolPaneCollapse, documents the two as a pair, and both panes now pass both halves.

Boot revealed instead of leaving the tree alone

bindPaneCollapse ran setPaneCollapsed(id, !open), and false there fronts the pane. Logs binds last, so it stole the active tab from the persisted tree. ⌃` then asked to collapse a terminal that was no longer active — the shared-zone branch declines that by design — so the first press did nothing. Boot now only ever collapses; the persisted tree already records which tab was active.

"Hide header" had two independent clobbers

normalize() deleted headerHidden whenever a zone dropped to one pane — a lone zone is headerless by default, so the flag looked redundant. It isn't: it's the user's standing preference for that zone, and dropping it brought the bar back the moment a pane rejoined. Re-adoption then pinned headerHidden: false unconditionally, which is right for a pane arriving somewhere new and wrong for a zone whose bar was deliberately hidden. Adoption now carries the destination zone's own setting, read before the insert since insertAtGroup pins the flag itself on a center drop.

Min-height

The terminal and logs carried a 7.5rem floor, so dragging the seam down jammed with a sliver of unusable terminal showing. Tool-panel zones now floor at the h-7 header strip, and releasing there minimizes the zone — it folds to its collapsed header, vertical rail or horizontal strip depending on the parent axis. The sliver size is never written, so restoring returns the previous height.

Verification

Eleven regression tests across the two toggle classes, all driven through the real bindings. Reverting the fixes turns them red — including the ⌃`-is-a-dead-key case and the missing opener.

That last one is worth calling out: the tests originally kept a local copy of each binding, and passed with the fix reverted. Both bindings moved into the store so the tests exercise production code, which is what caught the missing files/review opener at all.

1164 tests pass across pane-shell, app/chat, app/shell, store and app/contrib; eslint clean on the changed files.

Dragging the terminal to the bottom stacks logs into its zone, and every
tool-panel toggle broke in that stack.

Boot revealed instead of leaving the tree alone: bindPaneCollapse ran
setPaneCollapsed(id, !open), and `false` there fronts the pane. Logs binds
last, so it stole the active tab from the persisted tree. Ctrl-` then asked
to collapse a terminal that was no longer the active tab, the shared-zone
branch declined by design, and the key did nothing until the stack was
broken up. Boot now only ever collapses.

The toggles also asked the wrong question. !$terminalTakeover.get() flips a
boolean that has no idea what is on screen, so once anything else moved the
pane the press spent itself re-asserting a value the store already held.
toggleToolPane derives from the tree instead, and ctrl-`, Cmd+J's terminal
fallback, the statusbar button and the logs palette row all route through
it. The terminal cycle/close keys drop the same stale boolean.

bindPaneCollapse moves into the tree store as bindToolPaneCollapse so the
boot rule is testable against the real function rather than a copy.
Two independent clobbers threw the choice away, so "hide header" never
survived a close/reopen cycle.

normalize() deleted headerHidden whenever a zone dropped to one pane, on
the grounds that a lone zone is headerless by default. It is — but the flag
is the user's standing preference for that zone, not a redundant value, and
dropping it meant the bar returned the moment a pane rejoined (close the
stacked logs pane, toggle it back).

Re-adoption then pinned headerHidden false unconditionally. That is right
for a pane arriving somewhere new, where zero chrome leaves no handle to
drag or close, and wrong for a zone whose bar the user deliberately hid.
Adoption now carries the destination zone's own setting, read before the
insert since insertAtGroup pins the flag itself on a center drop.
The terminal and logs carried a 7.5rem minHeight, so dragging the seam down
jammed against a floor with a sliver of unusable terminal still showing.

Tool-panel zones now floor at COLLAPSED_ZONE_PX (the h-7 header strip)
instead of the generic 80px, and releasing at that floor minimizes the zone
rather than persisting the sliver — it folds to its collapsed header,
vertical rail or horizontal strip depending on the parent axis. The sliver
size is never written, so restoring returns the height it had before.
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

૮ >ﻌ< ა ci review

ran on feaa325

ℹ️ Info

Desktop E2E visual evidence · View test artifacts · View job

3 visual diffs.

inline evidence is publishing...

The terminal fix was only one instance. An audit of the other pane toggles
found ⌘G and ⌘J diverging the same way, proven with a probe: with review
stacked behind files in the right column, or either pane inside a minimized
zone, the store reads open while nothing is on screen, so the press
re-asserts a value it already held and the key does nothing.

isPaneVisible / togglePaneVisible replace the tool-panel-only pair and now
back every toggle. Close still routes through closeTreePane, so each pane
keeps its own semantics: a tool panel collapses to its rail, files and
review close through their store, anything else is dismissed.

files and review were bound with a closer and no opener, so the boolean went
stale as soon as anything but the toggle revealed them. bindPaneVisibility
moves into the tree store beside bindToolPaneCollapse, documents the two as a
pair, and both panes now pass both halves. Keeping the binding in the store
also means the tests drive the real function — the earlier copy in the test
file passed with the fix reverted, which is how the missing opener survived
the first pass.

setTreePaneHidden keeps its quiet path: a reactive unhide (a cwd arriving)
must not front or un-minimize over what the user is looking at. Only user
intent goes through the reveal path.
@OutThisLife OutThisLife changed the title Toggle the terminal wherever you put it, and keep the header hidden Toggle any pane wherever you put it, and keep the header hidden Aug 1, 2026
main reworked the same surface while this was open, so three hunks needed
deciding rather than accepting.

Logs became summon-only (#75862): the contribution only exists while $logsOpen
is on, docked as its OWN zone beside the terminal instead of a tab in its
strip. That supersedes the static logs pane and the bindToolPaneCollapse call
here — main already registers logs' closer/opener directly, so both were
dropped in favour of its version.

main also added a ⌘K "Toggle terminal" row reading $terminalTakeover, and
gave logs back a 7.5rem minHeight under a comment claiming the terminal's
sizing rule. Both are the bugs this branch fixes, so they move onto the
shared behaviour: the palette row reads isPaneVisible/togglePaneVisible like
every other pane toggle, and logs loses the floor so the comment is true —
the sash folds its zone to the rail instead of stranding a sliver.
@OutThisLife
OutThisLife enabled auto-merge August 1, 2026 06:26
@OutThisLife
OutThisLife merged commit 41e5567 into main Aug 1, 2026
31 checks passed
@OutThisLife
OutThisLife deleted the bb/toggle-terminal-persist branch August 1, 2026 06:32
@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/desktop Electron desktop app (apps/desktop/*) labels Aug 1, 2026
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
…minal-persist

Toggle any pane wherever you put it, and keep the header hidden
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/desktop Electron desktop app (apps/desktop/*) P3 Low — cosmetic, nice to have type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants