Skip to content

Commit bf9f557

Browse files
authored
Fix popup timestamps and sidebar return animation
Hide timestamp controls in popup shell windows and keep the terminal host sidebar width while root pages are shown so returning to terminal tabs does not replay the open animation.
1 parent 106e748 commit bf9f557

8 files changed

Lines changed: 59 additions & 9 deletions

File tree

components/Terminal.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ const TerminalComponent: React.FC<TerminalProps> = ({
9494
snippets,
9595
snippetPackages = [],
9696
compactToolbar = false,
97+
lineTimestampsAvailable = true,
9798
chainHosts = [],
9899
themePreviewId,
99100
knownHosts = [],
@@ -1209,7 +1210,7 @@ const TerminalComponent: React.FC<TerminalProps> = ({
12091210

12101211
useTerminalEffects({ CONNECTION_TIMEOUT, Error, XTERM_PERFORMANCE_CONFIG, applyUserCursorPreference, auth, autocompleteCloseRef, autocompleteInputRef, autocompleteKeyEventRef, captureTerminalLogData, clearTerminalCwd, commandBufferRef, connectionLogBufferRef, containerRef, createPromptLineBreakState, createReplaySafeTerminalLogSanitizer, createXTermRuntime, deferTerminalResizeRef, effectiveFontSize, effectiveFontWeight, effectiveTheme, error, executeSnippetCommand, fitAddonRef, fontFamilyId, fontSize, fontWeightFixupDoneRef, forceSyncRenderAfterResize, handleOsc52ReadRequest, handleTerminalDataCaptureOnce, hasConnectedRef, host, hotkeySchemeRef, identities, inWorkspace, isBootActiveRef, isBroadcastEnabledRef, isComposeBarOpen: effectiveComposeBarOpen, isFocusMode, isFocused, isLocalConnection, isNetworkDevice, isResizing: deferTerminalResize, isRestoringSelectionRef, isSearchOpen, isSerialConnection, isVisible, isVisibleRef, keyBindingsRef, keys, knownCwdRef, lastFittedSizeRef, lastToastedErrorRef, logger, mouseTrackingRef, onBroadcastInputRef, onCommandExecuted, onCommandSubmitted, onHotkeyActionRef, onSnippetShortkeyRef, onSnippetExecutorChange, onTerminalCwdChange, onTerminalFontSizeChange, paneLayoutKey, pendingAuthRef, pendingOutputScrollRef, prevIsResizingRef, promptLineBreakStateRef, resizeSession, resolveHostAuth, resolvedFontFamily, safeFit, searchAddonRef, serialConfig, serialLineBufferRef, serializeAddonRef, sessionId, sessionRef, sessionStarters, setError, setHasMouseTracking, setHasSelection, setIsCancelling, setIsDisconnectedDialogDismissed, setIsSearchOpen, setNeedsHostKeyVerification, setPendingHostKeyInfo, setPendingHostKeyRequestId, setProgressLogs, setProgressValue, setSelectionOverlayPosition, setShowLogs, setStatus, setTimeLeft, shouldEnableNativeUserInputAutoScroll, shouldProbeSessionCwd, snippetsRef, status, statusRef, sudoAutofillRef, t, teardown, termRef, terminalAltKeyOptions, terminalBackend, terminalContextActionsRef, terminalCwdTracker, terminalDataCapturedRef, terminalLogSanitizerRef, terminalSettings, terminalSettingsRef, toHostKeyInfo, toast, updateStatus, useEffect, useLayoutEffect, xtermRuntimeRef, zmodem, zmodemToastedRef });
12111212

1212-
return <TerminalView ctx={{ Activity, ArrowDownToLine, ArrowUpFromLine, Button, Clock3, Copy, Cpu, HardDrive, HoverCard, HoverCardContent, HoverCardTrigger, Maximize2, MemoryStick, Radio, Sparkles, TerminalAutocomplete, TerminalComposeBar, TerminalConnectionDialog, TerminalContextMenu, TerminalSearchBar, Tooltip, TooltipContent, TooltipTrigger, ZmodemOverwriteDialog, ZmodemProgressIndicator, auth, autocompleteAcceptTextRef, autocompleteCloseRef, autocompleteHostOs, autocompleteInputRef, autocompleteKeyEventRef, autocompleteRepositionRef, autocompleteSettings, chainProgress, cn, compactToolbar, containerRef, effectiveFontSize, effectiveFontWeight, effectiveTheme, error, executeSnippet, executeSnippetCommand, handleAddSelectionToAI, handleCancelConnect, handleCloseDisconnectedSession, handleCloseSearch, handleDismissDisconnectedDialog, handleDragEnter, handleDragLeave, handleDragOver, handleDrop, handleFindNext, handleFindPrevious, handleHostKeyAddAndContinue, handleHostKeyClose, handleHostKeyContinue, handleOsc52ReadResponse, handleRetry, handleSearch, handleSendYmodem, handleTopOverlayMouseDownCapture, hasMouseTracking, hasSelection, host, hotkeyScheme, inWorkspace, isBroadcastEnabled, isCancelling, isComposeBarOpen, isDraggingOver, isFocusMode, isLocalConnection, isSerialConnection, isSearchOpen, isSupportedOs, isSystemSidebarEligible, keyBindings, keys, knownCwdRef, needsHostKeyVerification, onAddSelectionToAI, onBroadcastInput, onCloseSession, onExpandToFocus, onOpenSystem, onSplitHorizontal, onSplitVertical, onToggleBroadcast, onUpdateHost: handleUpdateHostFromTerminal, osc52ReadPromptVisible, pendingHostKeyInfo, progressLogs, progressValue, renderControls, resolvedFontFamily, scrollToBottomAfterProgrammaticInput, searchMatchCount, selectionOverlayPosition, sessionId, sessionRef, setIsComposeBarOpen, setShowLogs, shouldShowConnectionDialog, showLogs, snippets, status, statusDotTone, sudoHintRef, sudoHintText: t("terminal.sudoHint.pressEnter"), t, termRef, terminalBackend, terminalContextActions, terminalCwdTracker, terminalPreviewVars, terminalSettings, timeLeft, toast, zmodem }} />;
1213+
return <TerminalView ctx={{ Activity, ArrowDownToLine, ArrowUpFromLine, Button, Clock3, Copy, Cpu, HardDrive, HoverCard, HoverCardContent, HoverCardTrigger, Maximize2, MemoryStick, Radio, Sparkles, TerminalAutocomplete, TerminalComposeBar, TerminalConnectionDialog, TerminalContextMenu, TerminalSearchBar, Tooltip, TooltipContent, TooltipTrigger, ZmodemOverwriteDialog, ZmodemProgressIndicator, auth, autocompleteAcceptTextRef, autocompleteCloseRef, autocompleteHostOs, autocompleteInputRef, autocompleteKeyEventRef, autocompleteRepositionRef, autocompleteSettings, chainProgress, cn, compactToolbar, lineTimestampsAvailable, containerRef, effectiveFontSize, effectiveFontWeight, effectiveTheme, error, executeSnippet, executeSnippetCommand, handleAddSelectionToAI, handleCancelConnect, handleCloseDisconnectedSession, handleCloseSearch, handleDismissDisconnectedDialog, handleDragEnter, handleDragLeave, handleDragOver, handleDrop, handleFindNext, handleFindPrevious, handleHostKeyAddAndContinue, handleHostKeyClose, handleHostKeyContinue, handleOsc52ReadResponse, handleRetry, handleSearch, handleSendYmodem, handleTopOverlayMouseDownCapture, hasMouseTracking, hasSelection, host, hotkeyScheme, inWorkspace, isBroadcastEnabled, isCancelling, isComposeBarOpen, isDraggingOver, isFocusMode, isLocalConnection, isSerialConnection, isSearchOpen, isSupportedOs, isSystemSidebarEligible, keyBindings, keys, knownCwdRef, needsHostKeyVerification, onAddSelectionToAI, onBroadcastInput, onCloseSession, onExpandToFocus, onOpenSystem, onSplitHorizontal, onSplitVertical, onToggleBroadcast, onUpdateHost: handleUpdateHostFromTerminal, osc52ReadPromptVisible, pendingHostKeyInfo, progressLogs, progressValue, renderControls, resolvedFontFamily, scrollToBottomAfterProgrammaticInput, searchMatchCount, selectionOverlayPosition, sessionId, sessionRef, setIsComposeBarOpen, setShowLogs, shouldShowConnectionDialog, showLogs, snippets, status, statusDotTone, sudoHintRef, sudoHintText: t("terminal.sudoHint.pressEnter"), t, termRef, terminalBackend, terminalContextActions, terminalCwdTracker, terminalPreviewVars, terminalSettings, timeLeft, toast, zmodem }} />;
12131214
};
12141215

12151216
const Terminal = memo(TerminalComponent, terminalPropsAreEqual);

components/TerminalPopupPage.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@ function TerminalPopupPageInner() {
306306
snippets={snippets}
307307
snippetPackages={snippetPackages}
308308
compactToolbar
309+
lineTimestampsAvailable={false}
309310
knownHosts={knownHosts}
310311
isVisible
311312
isFocused

components/terminal/TerminalView.test.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import test from "node:test";
22
import assert from "node:assert/strict";
3+
import { readFileSync } from "node:fs";
34

4-
import { getLineTimestampToggleHostUpdate } from "./TerminalView.tsx";
5+
import {
6+
getLineTimestampToggleHostUpdate,
7+
shouldShowLineTimestampToolbarToggle,
8+
} from "./TerminalView.tsx";
59

610
test("line timestamp toggle creates a persistent host update", () => {
711
const host = {
@@ -20,3 +24,16 @@ test("line timestamp toggle creates a persistent host update", () => {
2024
showLineTimestamps: false,
2125
});
2226
});
27+
28+
test("line timestamp toolbar toggle is hidden when timestamps are unavailable", () => {
29+
assert.equal(shouldShowLineTimestampToolbarToggle(false, () => {}), false);
30+
assert.equal(shouldShowLineTimestampToolbarToggle(true, () => {}), true);
31+
assert.equal(shouldShowLineTimestampToolbarToggle(undefined, () => {}), true);
32+
assert.equal(shouldShowLineTimestampToolbarToggle(true, undefined), false);
33+
});
34+
35+
test("popup terminals disable line timestamp controls", () => {
36+
const source = readFileSync(new URL("../TerminalPopupPage.tsx", import.meta.url), "utf8");
37+
38+
assert.match(source, /lineTimestampsAvailable=\{false\}/);
39+
});

components/terminal/TerminalView.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ export function getLineTimestampToggleHostUpdate<T extends HostLineTimestampTogg
2323
};
2424
}
2525

26+
export function shouldShowLineTimestampToolbarToggle(
27+
lineTimestampsAvailable: boolean | undefined,
28+
onUpdateHost: unknown,
29+
): boolean {
30+
return lineTimestampsAvailable !== false && Boolean(onUpdateHost);
31+
}
32+
2633
export function shouldEnableYmodemAction({
2734
isSerialConnection,
2835
status,
@@ -60,9 +67,9 @@ function terminalViewCtxEqual(
6067
}
6168

6269
function TerminalViewInner({ ctx }: { ctx: TerminalViewContext }) {
63-
const { Activity, Button, Clock3, Copy, Maximize2, Radio, Sparkles, TerminalAutocomplete, TerminalComposeBar, TerminalConnectionDialog, TerminalContextMenu, TerminalSearchBar, Tooltip, TooltipContent, TooltipTrigger, ZmodemOverwriteDialog, ZmodemProgressIndicator, auth, autocompleteAcceptTextRef, autocompleteCloseRef, autocompleteHostOs, autocompleteInputRef, autocompleteKeyEventRef, autocompleteRepositionRef, autocompleteSettings, chainProgress, cn, compactToolbar, containerRef, effectiveFontSize, effectiveFontWeight, effectiveTheme, error, executeSnippet, executeSnippetCommand, handleAddSelectionToAI, handleCancelConnect, handleCloseDisconnectedSession, handleCloseSearch, handleDismissDisconnectedDialog, handleDragEnter, handleDragLeave, handleDragOver, handleDrop, handleFindNext, handleFindPrevious, handleHostKeyAddAndContinue, handleHostKeyClose, handleHostKeyContinue, handleOsc52ReadResponse, handleRetry, handleSearch, handleSendYmodem, handleTopOverlayMouseDownCapture, hasMouseTracking, hasSelection, host, hotkeyScheme, inWorkspace, isBroadcastEnabled, isCancelling, isComposeBarOpen, isDraggingOver, isFocusMode, isLocalConnection, isSerialConnection, isSearchOpen, isSupportedOs, isSystemSidebarEligible, isVisible, keyBindings, keys, knownCwdRef, needsHostKeyVerification, onCloseSession, onExpandToFocus, onOpenSystem, onSplitHorizontal, onSplitVertical, onToggleBroadcast, onUpdateHost, osc52ReadPromptVisible, pendingHostKeyInfo, progressLogs, progressValue, renderControls, resolvedFontFamily, searchMatchCount, selectionOverlayPosition, sessionId, sessionRef, setIsComposeBarOpen, setShowLogs, shouldShowConnectionDialog, showLogs, snippets, status, statusDotTone, sudoHintRef, sudoHintText, t, termRef, terminalContextActions, terminalCwdTracker, terminalPreviewVars, terminalSettings, timeLeft, toast, zmodem } = ctx;
70+
const { Activity, Button, Clock3, Copy, Maximize2, Radio, Sparkles, TerminalAutocomplete, TerminalComposeBar, TerminalConnectionDialog, TerminalContextMenu, TerminalSearchBar, Tooltip, TooltipContent, TooltipTrigger, ZmodemOverwriteDialog, ZmodemProgressIndicator, auth, autocompleteAcceptTextRef, autocompleteCloseRef, autocompleteHostOs, autocompleteInputRef, autocompleteKeyEventRef, autocompleteRepositionRef, autocompleteSettings, chainProgress, cn, compactToolbar, lineTimestampsAvailable, containerRef, effectiveFontSize, effectiveFontWeight, effectiveTheme, error, executeSnippet, executeSnippetCommand, handleAddSelectionToAI, handleCancelConnect, handleCloseDisconnectedSession, handleCloseSearch, handleDismissDisconnectedDialog, handleDragEnter, handleDragLeave, handleDragOver, handleDrop, handleFindNext, handleFindPrevious, handleHostKeyAddAndContinue, handleHostKeyClose, handleHostKeyContinue, handleOsc52ReadResponse, handleRetry, handleSearch, handleSendYmodem, handleTopOverlayMouseDownCapture, hasMouseTracking, hasSelection, host, hotkeyScheme, inWorkspace, isBroadcastEnabled, isCancelling, isComposeBarOpen, isDraggingOver, isFocusMode, isLocalConnection, isSerialConnection, isSearchOpen, isSupportedOs, isSystemSidebarEligible, isVisible, keyBindings, keys, knownCwdRef, needsHostKeyVerification, onCloseSession, onExpandToFocus, onOpenSystem, onSplitHorizontal, onSplitVertical, onToggleBroadcast, onUpdateHost, osc52ReadPromptVisible, pendingHostKeyInfo, progressLogs, progressValue, renderControls, resolvedFontFamily, searchMatchCount, selectionOverlayPosition, sessionId, sessionRef, setIsComposeBarOpen, setShowLogs, shouldShowConnectionDialog, showLogs, snippets, status, statusDotTone, sudoHintRef, sudoHintText, t, termRef, terminalContextActions, terminalCwdTracker, terminalPreviewVars, terminalSettings, timeLeft, toast, zmodem } = ctx;
6471
const terminalContentTop = isSearchOpen ? "64px" : "30px";
65-
const showLineTimestampGutter = host.showLineTimestamps === true;
72+
const showLineTimestampGutter = lineTimestampsAvailable !== false && host.showLineTimestamps === true;
6673
const lineTimestampColor = resolveTerminalTimestampGutterColor(effectiveTheme.colors);
6774
const [lineTimestampGutterWidth, setLineTimestampGutterWidth] = useState(() => (
6875
resolveTerminalTimestampGutterWidth({ fontSize: effectiveFontSize })
@@ -154,7 +161,7 @@ function TerminalViewInner({ ctx }: { ctx: TerminalViewContext }) {
154161
statusDotTone,
155162
)}
156163
/>
157-
{onUpdateHost && (
164+
{shouldShowLineTimestampToolbarToggle(lineTimestampsAvailable, onUpdateHost) && (
158165
<Tooltip>
159166
<TooltipTrigger asChild>
160167
<button

components/terminal/terminalHelpers.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ export interface TerminalProps {
9191
snippetPackages?: string[];
9292
/** Minimal toolbar for popup terminals (compose, search, snippets only). */
9393
compactToolbar?: boolean;
94+
/** Line timestamps are unavailable in popup terminals that stream shell output without timestamp metadata. */
95+
lineTimestampsAvailable?: boolean;
9496
chainHosts?: Host[];
9597
themePreviewId?: string;
9698
knownHosts?: KnownHost[];

components/terminal/terminalMemo.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export const terminalPropsAreEqual = (
1212
&& prev.snippets === next.snippets
1313
&& prev.snippetPackages === next.snippetPackages
1414
&& prev.compactToolbar === next.compactToolbar
15+
&& prev.lineTimestampsAvailable === next.lineTimestampsAvailable
1516
&& prev.chainHosts === next.chainHosts
1617
&& getThemePreviewId(prev) === getThemePreviewId(next)
1718
&& prev.knownHosts === next.knownHosts

components/terminalLayer/TerminalHostTreeSidebar.test.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Object.defineProperty(globalThis, 'localStorage', {
1616
const {
1717
applyTerminalHostTreeHostRename,
1818
shouldShowTerminalHostHoverCard,
19+
getTerminalHostTreeHiddenSurfaceShellWidth,
1920
getTerminalHostTreeInitialLayoutWidth,
2021
getTerminalHostTreeLayoutTargetWidth,
2122
getTerminalHostTreeMeasuredLayoutWidth,
@@ -55,6 +56,12 @@ test('host tree layout target follows visible surface state', () => {
5556
assert.equal(getTerminalHostTreeLayoutTargetWidth(false, 240), 0);
5657
});
5758

59+
test('host tree hidden surface shell keeps the open width for return navigation', () => {
60+
assert.equal(getTerminalHostTreeHiddenSurfaceShellWidth(true, true, 240), 240);
61+
assert.equal(getTerminalHostTreeHiddenSurfaceShellWidth(false, true, 240), 0);
62+
assert.equal(getTerminalHostTreeHiddenSurfaceShellWidth(true, false, 240), 0);
63+
});
64+
5865
test('host tree layout starts collapsed so first mount can animate open', () => {
5966
assert.equal(getTerminalHostTreeInitialLayoutWidth(), 0);
6067
});
@@ -77,11 +84,13 @@ test('host tree layout width follows the animated shell via ResizeObserver', ()
7784
assert.doesNotMatch(source, /performance\.now\(\)/);
7885
});
7986

80-
test('host tree collapses instantly when hidden behind root pages', () => {
87+
test('host tree keeps shell width while hidden behind root pages', () => {
8188
const source = readFileSync(new URL('./TerminalHostTreeSidebar.tsx', import.meta.url), 'utf8');
8289

8390
assert.match(source, /isResizing \|\| !surfaceVisible/);
84-
assert.match(source, /if \(!surfaceVisible\) \{\s*setShellWidth\(0\);\s*terminalHostTreeStore\.setLayoutWidth\(0\);/);
91+
assert.match(source, /const hiddenSurfaceShellWidth = getTerminalHostTreeHiddenSurfaceShellWidth/);
92+
assert.match(source, /if \(!surfaceVisible\) \{\s*setShellWidth\(hiddenSurfaceShellWidth\);\s*terminalHostTreeStore\.setLayoutWidth\(0\);/);
93+
assert.doesNotMatch(source, /if \(!surfaceVisible\) \{\s*setShellWidth\(0\);/);
8594
});
8695

8796
test('host tree sidebar memo tracks surface visibility changes', () => {

components/terminalLayer/TerminalHostTreeSidebar.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,17 @@ export function getTerminalHostTreeLayoutTargetWidth(isVisible: boolean, display
164164
return isVisible ? displayWidth : 0;
165165
}
166166

167+
export function getTerminalHostTreeHiddenSurfaceShellWidth(
168+
isOpen: boolean,
169+
enabled: boolean,
170+
displayWidth: number,
171+
): number {
172+
return getTerminalHostTreeLayoutTargetWidth(
173+
isTerminalHostTreeSidebarVisible(isOpen, enabled, true),
174+
displayWidth,
175+
);
176+
}
177+
167178
export function getTerminalHostTreeInitialLayoutWidth(): number {
168179
return 0;
169180
}
@@ -945,6 +956,7 @@ const TerminalHostTreeSidebarInner: React.FC<TerminalHostTreeSidebarProps> = ({
945956

946957
const displayWidth = resizePreviewWidth ?? sidebarWidth;
947958
const targetLayoutWidth = getTerminalHostTreeLayoutTargetWidth(isVisible, displayWidth);
959+
const hiddenSurfaceShellWidth = getTerminalHostTreeHiddenSurfaceShellWidth(isOpen, enabled, displayWidth);
948960
const [shellWidth, setShellWidth] = useState(getTerminalHostTreeInitialLayoutWidth);
949961
const cancelSyncLayoutWidthRef = useRef<(() => void) | null>(null);
950962
const prevIsVisibleRef = useRef(isVisible);
@@ -1023,7 +1035,7 @@ const TerminalHostTreeSidebarInner: React.FC<TerminalHostTreeSidebarProps> = ({
10231035
}
10241036

10251037
if (!surfaceVisible) {
1026-
setShellWidth(0);
1038+
setShellWidth(hiddenSurfaceShellWidth);
10271039
terminalHostTreeStore.setLayoutWidth(0);
10281040
return;
10291041
}
@@ -1038,7 +1050,7 @@ const TerminalHostTreeSidebarInner: React.FC<TerminalHostTreeSidebarProps> = ({
10381050
cancelSyncLayoutWidthRef.current?.();
10391051
cancelSyncLayoutWidthRef.current = null;
10401052
};
1041-
}, [isResizing, surfaceVisible, syncLayoutWidthFromShell, targetLayoutWidth]);
1053+
}, [hiddenSurfaceShellWidth, isResizing, surfaceVisible, syncLayoutWidthFromShell, targetLayoutWidth]);
10421054

10431055
return (
10441056
<div

0 commit comments

Comments
 (0)