feat(titlebar): group a host's sessions into one tab, with a pinnable session panel (#318) - #323
Merged
Merged
Conversation
- Escape closes the hover list - hover intent holds open while a row's context menu or rename is active - exempt the row context menu from PickerSurface's outside-mousedown dismiss - chevron and compact row actions are keyboard-operable - fix the hover surface's dead zone against PickerSurface's own padding - extract lastActiveByHost/sessionTabHandlers wiring into hooks - prune lastActiveByHost, dedupe worstStatus/stackMemberLabels calls - Open in split closes the hover list - add hover-intent test coverage - drop narrative comments
Releasing hold (a row's context menu closing, or a rename ending) now re-arms the close timer itself, since the mouseleave that would have started it already fired and was skipped while held.
…ter left it Approach (b) closed the list 300ms after any menu action even with the pointer resting on it. useHoverIntent now tracks the last pointer position and its own anchor/surface refs, and only arms the close timer on hold-release when that position is outside both.
- DetachedPanePreview now uses sessionTabIcon/tabSurfaceStyle instead of its own copy of the distro-icon/local-terminal/StatusDot and active background/color/border logic - HostStackMenu resolves its shown member via the shared shownMember() instead of its own find-or-first - StackTab's worst-status corner badge uses the shared StatusDot (corner + halo) instead of a second hand-built dot - HostSessionsPanel's two header icon buttons share one PanelIconButton instead of repeating the same class string twice - HostSessionRows's compact/panel padding classes share one rowPadding value instead of repeating the ternary twice - sessionStatusLine's switch gets a default: ... satisfies never guard so a future status is a compile error, not a silent undefined - usePaneDragController drops a redundant useLayoutStore.getState() call, reusing the layout snapshot already captured in onUp
openInSplit already had unit tests; pinHostList did not.
The pinned panel's Open in split could pick an in-split session as its base, and a drag from an in-split row could split it again, so one session ended up in two split tabs and the snapshot persisted it. - createSplitTab and splitPane refuse a session already in any split tab - the drag controller focuses a session that already lives in a split instead of splitting it again - panel rows start no drag when in a split, and Open in split only offers unsplit members as its base
Docker/LXC exec sessions copy their parent's connectionId, so they joined the parent's stack and headed its pill with "exec: nginx". Every ephemeral serial session shares "serial-ephemeral", so different ports stacked together. Every grouping site now goes through one stackGroupKey(session): the session itself for exec and ephemeral serial, the connection otherwise. The stack's host name comes from a non-exec member, and member numbers follow open order rather than bar order, so reordering no longer renumbers.
- a row menu's hold is released when the list unmounts or closes under it, so the list closes on mouse-leave again - the list closes when its host gets pinned while it is open - a drag that started on a list row draws no cue on the bar - in-split panel rows set no drop target, and leaving the panel rows clears it, so a drop elsewhere no longer reorders - the list's close callback is stable across titlebar renders
… new-session action - a panel row's status is one truncated line with the full text in its title, and the in-split marker shares that run - a stack pill has no close button (Close all N is in its menu) - the stack chevron carries aria-label, aria-haspopup and aria-expanded - New session on <host> is one helper used by the pill menu, the list footer and the panel header
- the pane menu's Split right/down picks a session that is in no split tab, instead of one the store now refuses (which left focus on a hidden session) - openSessions skips sessions already in a split and, with fewer than two left, focuses the existing pane instead of building a tab - hydrate drops a session's pane from any later tab of an older corrupted snapshot, collapsing tabs left with one pane - middle-click on a stack pill no longer closes its hidden shown member
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #318.
Sessions on the same host collapse into one titlebar pill; its session list can be pinned as a panel beside the terminal — the shape the reporter confirmed on the issue.
What it does
prod-web-01,prod-web-01 (2)), like Termius.PickerSurface, so it flips near the screen edge and becomes a bottom sheet on Android.How it is built
layoutStore.titlebarOrder; no stack key is ever stored, so nothing migrates and the workspace snapshot is unchanged.stackGroupKeyis the single grouping rule (per connection, except container-exec and ephemeral-serial sessions, which key on themselves).hydraterepairs snapshots saved by older builds that already contain that state.Checks
tscclean.Known minor
Once one of a host's sessions sits in a split, the pill numbers only the unsplit members while the panel numbers every row, so the same session can read "(2)" on the pill and "(3)" in the panel.