Skip to content

Commit abdf13d

Browse files
committed
feat(tui): compact vertical tab rail
Drop the duplicate project name from tab detail lines under a project header, hide the empty detail row, remove the blank row between tabs of the same project, and show a bullet for idle tabs.
1 parent 89ddb5f commit abdf13d

2 files changed

Lines changed: 76 additions & 67 deletions

File tree

‎PATCH.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ The hover `✕` is gone from vertical tabs (middle-click still closes); horizont
9797

9898
The expanded vertical rail groups tabs under project headers and nests child sessions under their parent tab; manual tab reordering is disabled while `tabs.layout` is vertical.
9999

100+
## Vertical Tab Compact Layout (TUI)
101+
102+
In the expanded vertical rail, the project name shows only in the group header, not again on each tab's detail line; the detail row renders only when there is a worktree branch (`⎇ branch`). Tabs under the same project header sit directly under each other with no blank row (and no separator pulse between them), and idle tabs show a `•` in the status column. Compact rail, tooltips, and horizontal tabs are unchanged.
103+
100104
## Session List Preview (TUI) [NOT MIGRATED]
101105

102106
The session list (`<leader>l`) previews the highlighted session's latest messages on terminals 128 columns or wider.

‎packages/tui/src/component/session-tabs.tsx‎

Lines changed: 72 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -583,15 +583,15 @@ function VerticalSessionTabs(props: {
583583
})
584584
const [hoverY, setHoverY] = createSignal(0)
585585
const [scrollTop, setScrollTop] = createSignal(0)
586-
const detail = (sessionID: string) => {
586+
const detail = (sessionID: string, includeProject = true) => {
587587
const fixture = tabs.detail?.(sessionID)
588588
if (fixture !== undefined) return fixture
589589
const session = data?.session.get(sessionID)
590590
const project = session ? data?.project.get(session.projectID) : undefined
591591
const vcs = session ? data?.location.vcs.info(session.location) : undefined
592592
const location = session ? data?.location.info(session.location) : undefined
593593
return sessionTabDetail(
594-
projectName(project, session?.location.directory) ?? "",
594+
(includeProject ? projectName(project, session?.location.directory) : undefined) ?? "",
595595
vcs?.branch.current,
596596
vcs?.branch.default,
597597
!!location && location.project.directory !== location.project.canonical,
@@ -623,9 +623,7 @@ function VerticalSessionTabs(props: {
623623
// Wrapped titles make tab heights vary, so positions come from the laid-out boxes.
624624
const boxes = new Map<string, BoxRenderable>()
625625
let addRow: BoxRenderable | undefined
626-
const highlightColor = createMemo(() =>
627-
tint(background(), actionHovered(), actionHovered().a),
628-
)
626+
const highlightColor = createMemo(() => tint(background(), actionHovered(), actionHovered().a))
629627
const highlighted = (sessionID: string | undefined) =>
630628
sessionID !== undefined && (activeID() === sessionID || hovered() === sessionID || dragging() === sessionID)
631629
const addHighlighted = () => newTab() || addHovered()
@@ -735,7 +733,7 @@ function VerticalSessionTabs(props: {
735733
backgroundColor={background()}
736734
scrollbarOptions={{ visible: false }}
737735
>
738-
<box flexShrink={0} flexDirection="column" gap={1} paddingY={compact() ? 1 : 0}>
736+
<box flexShrink={0} flexDirection="column" paddingY={compact() ? 1 : 0}>
739737
<For each={items()}>
740738
{(tab, index) => {
741739
const selected = () => activeID() === tab.sessionID
@@ -749,7 +747,7 @@ function VerticalSessionTabs(props: {
749747
const titleWidth = () => (hovered() === tab.sessionID ? hoveredTitleWidth() : restingTitleWidth())
750748
const title = () => (props.controller ? undefined : session()?.title) ?? tab.title ?? "Untitled session"
751749
const titleParts = createMemo(() => Locale.graphemes(title()))
752-
const tabDetail = createMemo(() => detail(tab.sessionID))
750+
const tabDetail = createMemo(() => detail(tab.sessionID, compact() || !group(tab.sessionID)))
753751
const visibleDetail = createMemo(() => Locale.takeWidth(tabDetail(), titleWidth()))
754752
const visibleDetailParts = createMemo(() => Locale.graphemes(visibleDetail()))
755753
const detailFades = createMemo(
@@ -840,9 +838,7 @@ function VerticalSessionTabs(props: {
840838
const separatorUpperColor = createMemo(() =>
841839
tint(background(), previousGlowHue(), 0.1 * previousGlowLevel()),
842840
)
843-
const separatorLowerColor = createMemo(() =>
844-
tint(background(), glowHue(), 0.12 * glowLevel()),
845-
)
841+
const separatorLowerColor = createMemo(() => tint(background(), glowHue(), 0.12 * glowLevel()))
846842
const titleColor = (index: number) => {
847843
const level = titleGlow.value().level
848844
return level === 0
@@ -856,8 +852,13 @@ function VerticalSessionTabs(props: {
856852
return label && label !== (before ? group(before.sessionID) : undefined) ? label : undefined
857853
})
858854
const childRows = createMemo(() => children(tab.sessionID))
855+
const sameGroup = createMemo(() => {
856+
const before = previous()
857+
const label = group(tab.sessionID)
858+
return !compact() && !!before && !!label && label === group(before.sessionID)
859+
})
859860
return (
860-
<box flexDirection="column" width="100%">
861+
<box flexDirection="column" width="100%" marginTop={index() > 0 && !sameGroup() ? 1 : 0}>
861862
<Show when={!compact() && header()}>
862863
{(label) => (
863864
<box paddingLeft={1} paddingRight={1} marginBottom={1}>
@@ -914,9 +915,7 @@ function VerticalSessionTabs(props: {
914915
edge="top"
915916
width={width()}
916917
color={pulseBackground()}
917-
background={
918-
highlighted(items()[index() - 1]?.sessionID) ? highlightColor() : background()
919-
}
918+
background={highlighted(items()[index() - 1]?.sessionID) ? highlightColor() : background()}
920919
/>
921920
<SessionTabHalfRow
922921
top={1}
@@ -961,32 +960,34 @@ function VerticalSessionTabs(props: {
961960
</box>
962961
</Show>
963962
<Show when={!compact()}>
964-
<TabPulse
965-
top={-1}
966-
edge="above"
967-
enabled={animations()}
968-
active={runs()}
969-
outerActive={previousRuns()}
970-
promptPulse={status().promptPulse}
971-
outerPromptPulse={previousStatus().promptPulse}
972-
complete={complete() && !status().attention}
973-
outerComplete={previousStatus().complete && !previousStatus().attention}
974-
glow={glows()}
975-
outerGlow={previousGlows()}
976-
color={separatorLowerPulseColor()}
977-
width={indicatorWidth}
978-
outerColor={separatorUpperPulseColor()}
979-
flashColor={tint(background(), theme.text.base, 0.22)}
980-
outerFlashColor={tint(background(), theme.text.base, 0.18)}
981-
flashTail={8}
982-
glowColor={separatorLowerColor()}
983-
outerGlowColor={separatorUpperColor()}
984-
glowTail={8}
985-
outerGlowTail={5}
986-
completionColor={separatorLowerColor()}
987-
outerCompletionColor={separatorUpperColor()}
988-
backgroundColor={background()}
989-
/>
963+
<Show when={!sameGroup()}>
964+
<TabPulse
965+
top={-1}
966+
edge="above"
967+
enabled={animations()}
968+
active={runs()}
969+
outerActive={previousRuns()}
970+
promptPulse={status().promptPulse}
971+
outerPromptPulse={previousStatus().promptPulse}
972+
complete={complete() && !status().attention}
973+
outerComplete={previousStatus().complete && !previousStatus().attention}
974+
glow={glows()}
975+
outerGlow={previousGlows()}
976+
color={separatorLowerPulseColor()}
977+
width={indicatorWidth}
978+
outerColor={separatorUpperPulseColor()}
979+
flashColor={tint(background(), theme.text.base, 0.22)}
980+
outerFlashColor={tint(background(), theme.text.base, 0.18)}
981+
flashTail={8}
982+
glowColor={separatorLowerColor()}
983+
outerGlowColor={separatorUpperColor()}
984+
glowTail={8}
985+
outerGlowTail={5}
986+
completionColor={separatorLowerColor()}
987+
outerCompletionColor={separatorUpperColor()}
988+
backgroundColor={background()}
989+
/>
990+
</Show>
990991
<Show when={index() === items().length - 1 && childRows().length === 0}>
991992
<TabPulse
992993
bottom={-1}
@@ -1034,6 +1035,7 @@ function VerticalSessionTabs(props: {
10341035
<TabIndicator
10351036
status={status()}
10361037
label={sessionTabNumberLabel(index())}
1038+
idleLabel="•"
10371039
width={numberWidth()}
10381040
color={numberColor()}
10391041
unreadColor={tabFeedbackColor(status(), theme) ?? unreadColor()}
@@ -1069,34 +1071,36 @@ function VerticalSessionTabs(props: {
10691071
</title_shimmer>
10701072
</box>
10711073
</box>
1072-
<box height={1} width="100%" position="relative" flexDirection="row">
1073-
<TabPulse
1074-
enabled={animations()}
1075-
active={runs()}
1076-
promptPulse={status().promptPulse}
1077-
complete={complete() && !status().attention}
1078-
glow={glows()}
1079-
color={detailPulseColor()}
1080-
width={indicatorWidth}
1081-
glowColor={detailGlowColor()}
1082-
glowTail={10}
1083-
flashColor={detailFlashColor()}
1084-
flashTail={8}
1085-
completionColor={detailGlowColor()}
1086-
backgroundColor={pulseBackground()}
1087-
/>
1088-
<box zIndex={1} width="100%" flexDirection="row" paddingLeft={prefixWidth()} paddingRight={2}>
1089-
<text fg={detailColor()} wrapMode="none" selectable={false}>
1090-
<Show when={detailFades()} fallback={visibleDetail()}>
1091-
<For each={visibleDetailParts()}>
1092-
{(character, index) => (
1093-
<span style={{ fg: detailTextColor(index()) }}>{character}</span>
1094-
)}
1095-
</For>
1096-
</Show>
1097-
</text>
1074+
<Show when={tabDetail()}>
1075+
<box height={1} width="100%" position="relative" flexDirection="row">
1076+
<TabPulse
1077+
enabled={animations()}
1078+
active={runs()}
1079+
promptPulse={status().promptPulse}
1080+
complete={complete() && !status().attention}
1081+
glow={glows()}
1082+
color={detailPulseColor()}
1083+
width={indicatorWidth}
1084+
glowColor={detailGlowColor()}
1085+
glowTail={10}
1086+
flashColor={detailFlashColor()}
1087+
flashTail={8}
1088+
completionColor={detailGlowColor()}
1089+
backgroundColor={pulseBackground()}
1090+
/>
1091+
<box zIndex={1} width="100%" flexDirection="row" paddingLeft={prefixWidth()} paddingRight={2}>
1092+
<text fg={detailColor()} wrapMode="none" selectable={false}>
1093+
<Show when={detailFades()} fallback={visibleDetail()}>
1094+
<For each={visibleDetailParts()}>
1095+
{(character, index) => (
1096+
<span style={{ fg: detailTextColor(index()) }}>{character}</span>
1097+
)}
1098+
</For>
1099+
</Show>
1100+
</text>
1101+
</box>
10981102
</box>
1099-
</box>
1103+
</Show>
11001104
</Show>
11011105
</box>
11021106
<Show when={!compact()}>
@@ -1138,6 +1142,7 @@ function VerticalSessionTabs(props: {
11381142
<box
11391143
ref={(element: BoxRenderable) => (addRow = element)}
11401144
height={1}
1145+
marginTop={1}
11411146
width="100%"
11421147
position="relative"
11431148
flexDirection="row"

0 commit comments

Comments
 (0)