mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-06-01 14:42:51 -07:00
refactor(ui): extract SessionBrowser list components
This commit is contained in:
@@ -6,9 +6,7 @@
|
||||
|
||||
import type React from 'react';
|
||||
import { useState, useCallback, useMemo, useEffect, useRef } from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import { Colors } from '../colors.js';
|
||||
import { Box } from 'ink';
|
||||
import { useTerminalSize } from '../hooks/useTerminalSize.js';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import path from 'node:path';
|
||||
@@ -107,239 +105,16 @@ export interface SessionBrowserState {
|
||||
const SESSIONS_PER_PAGE = 20;
|
||||
// Approximate total width reserved for non-message columns and separators
|
||||
// (prefix, index, message count, age, pipes, and padding) in a session row.
|
||||
// If the SessionItem layout changes, update this accordingly.
|
||||
const FIXED_SESSION_COLUMNS_WIDTH = 30;
|
||||
|
||||
import { SearchModeDisplay } from './SessionBrowser/SearchModeDisplay.js';
|
||||
import { NavigationHelp } from './SessionBrowser/NavigationHelp.js';
|
||||
import { SessionListHeader } from './SessionBrowser/SessionListHeader.js';
|
||||
import { NoResultsDisplay } from './SessionBrowser/NoResultsDisplay.js';
|
||||
import { SessionBrowserLoading } from './SessionBrowser/SessionBrowserLoading.js';
|
||||
import { SessionBrowserError } from './SessionBrowser/SessionBrowserError.js';
|
||||
import { SessionBrowserEmpty } from './SessionBrowser/SessionBrowserEmpty.js';
|
||||
import { SessionList } from './SessionBrowser/SessionList.js';
|
||||
import { sortSessions, filterSessions } from './SessionBrowser/utils.js';
|
||||
|
||||
/**
|
||||
* Table header component with column labels and scroll indicators.
|
||||
*/
|
||||
const SessionTableHeader = ({
|
||||
state,
|
||||
}: {
|
||||
state: SessionBrowserState;
|
||||
}): React.JSX.Element => (
|
||||
<Box flexDirection="row" marginTop={1}>
|
||||
<Text>{state.scrollOffset > 0 ? <Text>▲ </Text> : ' '}</Text>
|
||||
|
||||
<Box width={5} flexShrink={0}>
|
||||
<Text color={Colors.Gray} bold>
|
||||
Index
|
||||
</Text>
|
||||
</Box>
|
||||
<Text color={Colors.Gray}> │ </Text>
|
||||
<Box width={4} flexShrink={0}>
|
||||
<Text color={Colors.Gray} bold>
|
||||
Msgs
|
||||
</Text>
|
||||
</Box>
|
||||
<Text color={Colors.Gray}> │ </Text>
|
||||
<Box width={4} flexShrink={0}>
|
||||
<Text color={Colors.Gray} bold>
|
||||
Age
|
||||
</Text>
|
||||
</Box>
|
||||
<Text color={Colors.Gray}> │ </Text>
|
||||
<Box flexShrink={0}>
|
||||
<Text color={Colors.Gray} bold>
|
||||
{state.searchQuery ? 'Match' : 'Name'}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
/**
|
||||
* Match snippet display component for search results.
|
||||
*/
|
||||
const MatchSnippetDisplay = ({
|
||||
session,
|
||||
textColor,
|
||||
}: {
|
||||
session: SessionInfo;
|
||||
textColor: (color?: string) => string;
|
||||
}): React.JSX.Element | null => {
|
||||
if (!session.matchSnippets || session.matchSnippets.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const firstMatch = session.matchSnippets[0];
|
||||
const rolePrefix = firstMatch.role === 'user' ? 'You: ' : 'Gemini:';
|
||||
const roleColor = textColor(
|
||||
firstMatch.role === 'user' ? Colors.AccentGreen : Colors.AccentBlue,
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Text color={roleColor} bold>
|
||||
{rolePrefix}{' '}
|
||||
</Text>
|
||||
{firstMatch.before}
|
||||
<Text color={textColor(Colors.AccentRed)} bold>
|
||||
{firstMatch.match}
|
||||
</Text>
|
||||
{firstMatch.after}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Individual session row component.
|
||||
*/
|
||||
const SessionItem = ({
|
||||
session,
|
||||
state,
|
||||
terminalWidth,
|
||||
formatRelativeTime,
|
||||
}: {
|
||||
session: SessionInfo;
|
||||
state: SessionBrowserState;
|
||||
terminalWidth: number;
|
||||
formatRelativeTime: (dateString: string, style: 'short' | 'long') => string;
|
||||
}): React.JSX.Element => {
|
||||
const originalIndex =
|
||||
state.startIndex + state.visibleSessions.indexOf(session);
|
||||
const isActive = originalIndex === state.activeIndex;
|
||||
const isDisabled = session.isCurrentSession;
|
||||
const textColor = (c: string = Colors.Foreground) => {
|
||||
if (isDisabled) {
|
||||
return Colors.Gray;
|
||||
}
|
||||
return isActive ? theme.ui.focus : c;
|
||||
};
|
||||
|
||||
const prefix = isActive ? '❯ ' : ' ';
|
||||
let additionalInfo = '';
|
||||
let matchDisplay = null;
|
||||
|
||||
// Add "(current)" label for the current session
|
||||
if (session.isCurrentSession) {
|
||||
additionalInfo = ' (current)';
|
||||
}
|
||||
|
||||
// Show match snippets if searching and matches exist
|
||||
if (
|
||||
state.searchQuery &&
|
||||
session.matchSnippets &&
|
||||
session.matchSnippets.length > 0
|
||||
) {
|
||||
matchDisplay = (
|
||||
<MatchSnippetDisplay session={session} textColor={textColor} />
|
||||
);
|
||||
|
||||
if (session.matchCount && session.matchCount > 1) {
|
||||
additionalInfo += ` (+${session.matchCount - 1} more)`;
|
||||
}
|
||||
}
|
||||
|
||||
// Reserve a few characters for metadata like " (current)" so the name doesn't wrap awkwardly.
|
||||
const reservedForMeta = additionalInfo ? additionalInfo.length + 1 : 0;
|
||||
const availableMessageWidth = Math.max(
|
||||
20,
|
||||
terminalWidth - FIXED_SESSION_COLUMNS_WIDTH - reservedForMeta,
|
||||
);
|
||||
|
||||
const truncatedMessage =
|
||||
matchDisplay ||
|
||||
(session.displayName.length === 0 ? (
|
||||
<Text color={textColor(Colors.Gray)} dimColor>
|
||||
(No messages)
|
||||
</Text>
|
||||
) : session.displayName.length > availableMessageWidth ? (
|
||||
session.displayName.slice(0, availableMessageWidth - 1) + '…'
|
||||
) : (
|
||||
session.displayName
|
||||
));
|
||||
|
||||
return (
|
||||
<Box
|
||||
flexDirection="row"
|
||||
backgroundColor={isActive ? theme.background.focus : undefined}
|
||||
>
|
||||
<Text color={textColor()} dimColor={isDisabled}>
|
||||
{prefix}
|
||||
</Text>
|
||||
<Box width={5}>
|
||||
<Text color={textColor()} dimColor={isDisabled}>
|
||||
#{originalIndex + 1}
|
||||
</Text>
|
||||
</Box>
|
||||
<Text color={textColor(Colors.Gray)} dimColor={isDisabled}>
|
||||
{' '}
|
||||
│{' '}
|
||||
</Text>
|
||||
<Box width={4}>
|
||||
<Text color={textColor()} dimColor={isDisabled}>
|
||||
{session.messageCount}
|
||||
</Text>
|
||||
</Box>
|
||||
<Text color={textColor(Colors.Gray)} dimColor={isDisabled}>
|
||||
{' '}
|
||||
│{' '}
|
||||
</Text>
|
||||
<Box width={4}>
|
||||
<Text color={textColor()} dimColor={isDisabled}>
|
||||
{formatRelativeTime(session.lastUpdated, 'short')}
|
||||
</Text>
|
||||
</Box>
|
||||
<Text color={textColor(Colors.Gray)} dimColor={isDisabled}>
|
||||
{' '}
|
||||
│{' '}
|
||||
</Text>
|
||||
<Box flexGrow={1}>
|
||||
<Text color={textColor(Colors.Comment)} dimColor={isDisabled}>
|
||||
{truncatedMessage}
|
||||
{additionalInfo && (
|
||||
<Text color={textColor(Colors.Gray)} dimColor bold={false}>
|
||||
{additionalInfo}
|
||||
</Text>
|
||||
)}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Session list container component.
|
||||
*/
|
||||
const SessionList = ({
|
||||
state,
|
||||
formatRelativeTime,
|
||||
}: {
|
||||
state: SessionBrowserState;
|
||||
formatRelativeTime: (dateString: string, style: 'short' | 'long') => string;
|
||||
}): React.JSX.Element => (
|
||||
<Box flexDirection="column">
|
||||
{/* Table Header */}
|
||||
<Box flexDirection="column">
|
||||
{!state.isSearchMode && <NavigationHelp />}
|
||||
<SessionTableHeader state={state} />
|
||||
</Box>
|
||||
|
||||
{state.visibleSessions.map((session) => (
|
||||
<SessionItem
|
||||
key={session.id}
|
||||
session={session}
|
||||
state={state}
|
||||
terminalWidth={state.terminalWidth}
|
||||
formatRelativeTime={formatRelativeTime}
|
||||
/>
|
||||
))}
|
||||
|
||||
<Text color={Colors.Gray}>
|
||||
{state.endIndex < state.totalSessions ? <>▼</> : <Text dimColor>▼</Text>}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
|
||||
/**
|
||||
* Hook to manage all SessionBrowser state.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import { Text } from 'ink';
|
||||
import { Colors } from '../../colors.js';
|
||||
import type { SessionInfo } from '../../../utils/sessionUtils.js';
|
||||
|
||||
/**
|
||||
* Match snippet display component for search results.
|
||||
*/
|
||||
export const MatchSnippetDisplay = ({
|
||||
session,
|
||||
textColor,
|
||||
}: {
|
||||
session: SessionInfo;
|
||||
textColor: (color?: string) => string;
|
||||
}): React.JSX.Element | null => {
|
||||
if (!session.matchSnippets || session.matchSnippets.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const firstMatch = session.matchSnippets[0];
|
||||
const rolePrefix = firstMatch.role === 'user' ? 'You: ' : 'Gemini:';
|
||||
const roleColor = textColor(
|
||||
firstMatch.role === 'user' ? Colors.AccentGreen : Colors.AccentBlue,
|
||||
);
|
||||
|
||||
return (
|
||||
<Text>
|
||||
<Text color={roleColor} bold>
|
||||
{rolePrefix}{' '}
|
||||
</Text>
|
||||
{firstMatch.before}
|
||||
<Text color={textColor(Colors.AccentRed)} bold>
|
||||
{firstMatch.match}
|
||||
</Text>
|
||||
{firstMatch.after}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { render } from '../../../test-utils/render.js';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { SessionList } from './SessionList.js';
|
||||
import { SessionItem } from './SessionItem.js';
|
||||
import { SessionTableHeader } from './SessionTableHeader.js';
|
||||
import { MatchSnippetDisplay } from './MatchSnippetDisplay.js';
|
||||
import type { SessionBrowserState } from '../SessionBrowser.js';
|
||||
import type { SessionInfo } from '../../../utils/sessionUtils.js';
|
||||
|
||||
describe('SessionBrowser List Components', () => {
|
||||
const mockSession: SessionInfo = {
|
||||
id: '1',
|
||||
file: 'session-1',
|
||||
fileName: 'session-1.json',
|
||||
startTime: new Date().toISOString(),
|
||||
displayName: 'Test Session',
|
||||
firstUserMessage: 'Test Session',
|
||||
messageCount: 5,
|
||||
lastUpdated: new Date().toISOString(),
|
||||
isCurrentSession: false,
|
||||
index: 1,
|
||||
};
|
||||
|
||||
const mockState = {
|
||||
totalSessions: 1,
|
||||
startIndex: 0,
|
||||
endIndex: 1,
|
||||
visibleSessions: [mockSession],
|
||||
activeIndex: 0,
|
||||
scrollOffset: 0,
|
||||
terminalWidth: 80,
|
||||
searchQuery: '',
|
||||
isSearchMode: false,
|
||||
} as SessionBrowserState;
|
||||
|
||||
it('SessionTableHeader renders correctly', async () => {
|
||||
const { lastFrame, waitUntilReady } = render(
|
||||
<SessionTableHeader state={mockState} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('MatchSnippetDisplay returns null when no snippets', () => {
|
||||
const { lastFrame } = render(
|
||||
<MatchSnippetDisplay session={mockSession} textColor={(c) => c || ''} />,
|
||||
);
|
||||
expect(lastFrame({ allowEmpty: true })).toBe('');
|
||||
});
|
||||
|
||||
it('MatchSnippetDisplay renders correctly with snippets', async () => {
|
||||
const sessionWithSnippets = {
|
||||
...mockSession,
|
||||
matchSnippets: [
|
||||
{
|
||||
role: 'user' as const,
|
||||
before: 'hello ',
|
||||
match: 'world',
|
||||
after: ' !',
|
||||
},
|
||||
],
|
||||
};
|
||||
const { lastFrame, waitUntilReady } = render(
|
||||
<MatchSnippetDisplay
|
||||
session={sessionWithSnippets}
|
||||
textColor={(c) => c || ''}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('SessionItem renders correctly', async () => {
|
||||
const { lastFrame, waitUntilReady } = render(
|
||||
<SessionItem
|
||||
session={mockSession}
|
||||
state={mockState}
|
||||
terminalWidth={80}
|
||||
formatRelativeTime={() => '10m ago'}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('SessionList renders correctly', async () => {
|
||||
const { lastFrame, waitUntilReady } = render(
|
||||
<SessionList state={mockState} formatRelativeTime={() => '10m ago'} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { Colors } from '../../colors.js';
|
||||
import { theme } from '../../semantic-colors.js';
|
||||
import type { SessionBrowserState } from '../SessionBrowser.js';
|
||||
import type { SessionInfo } from '../../../utils/sessionUtils.js';
|
||||
import { MatchSnippetDisplay } from './MatchSnippetDisplay.js';
|
||||
|
||||
const FIXED_SESSION_COLUMNS_WIDTH = 30;
|
||||
|
||||
/**
|
||||
* Individual session row component.
|
||||
*/
|
||||
export const SessionItem = ({
|
||||
session,
|
||||
state,
|
||||
terminalWidth,
|
||||
formatRelativeTime,
|
||||
}: {
|
||||
session: SessionInfo;
|
||||
state: SessionBrowserState;
|
||||
terminalWidth: number;
|
||||
formatRelativeTime: (dateString: string, style: 'short' | 'long') => string;
|
||||
}): React.JSX.Element => {
|
||||
const originalIndex =
|
||||
state.startIndex + state.visibleSessions.indexOf(session);
|
||||
const isActive = originalIndex === state.activeIndex;
|
||||
const isDisabled = session.isCurrentSession;
|
||||
const textColor = (c: string = Colors.Foreground) => {
|
||||
if (isDisabled) {
|
||||
return Colors.Gray;
|
||||
}
|
||||
return isActive ? theme.ui.focus : c;
|
||||
};
|
||||
|
||||
const prefix = isActive ? '❯ ' : ' ';
|
||||
let additionalInfo = '';
|
||||
let matchDisplay = null;
|
||||
|
||||
// Add "(current)" label for the current session
|
||||
if (session.isCurrentSession) {
|
||||
additionalInfo = ' (current)';
|
||||
}
|
||||
|
||||
// Show match snippets if searching and matches exist
|
||||
if (
|
||||
state.searchQuery &&
|
||||
session.matchSnippets &&
|
||||
session.matchSnippets.length > 0
|
||||
) {
|
||||
matchDisplay = (
|
||||
<MatchSnippetDisplay session={session} textColor={textColor} />
|
||||
);
|
||||
|
||||
if (session.matchCount && session.matchCount > 1) {
|
||||
additionalInfo += ` (+${session.matchCount - 1} more)`;
|
||||
}
|
||||
}
|
||||
|
||||
// Reserve a few characters for metadata like " (current)" so the name doesn't wrap awkwardly.
|
||||
const reservedForMeta = additionalInfo ? additionalInfo.length + 1 : 0;
|
||||
const availableMessageWidth = Math.max(
|
||||
20,
|
||||
terminalWidth - FIXED_SESSION_COLUMNS_WIDTH - reservedForMeta,
|
||||
);
|
||||
|
||||
const truncatedMessage =
|
||||
matchDisplay ||
|
||||
(session.displayName.length === 0 ? (
|
||||
<Text color={textColor(Colors.Gray)} dimColor>
|
||||
(No messages)
|
||||
</Text>
|
||||
) : session.displayName.length > availableMessageWidth ? (
|
||||
session.displayName.slice(0, availableMessageWidth - 1) + '…'
|
||||
) : (
|
||||
session.displayName
|
||||
));
|
||||
|
||||
return (
|
||||
<Box
|
||||
flexDirection="row"
|
||||
backgroundColor={isActive ? theme.background.focus : undefined}
|
||||
>
|
||||
<Text color={textColor()} dimColor={isDisabled}>
|
||||
{prefix}
|
||||
</Text>
|
||||
<Box width={5}>
|
||||
<Text color={textColor()} dimColor={isDisabled}>
|
||||
#{originalIndex + 1}
|
||||
</Text>
|
||||
</Box>
|
||||
<Text color={textColor(Colors.Gray)} dimColor={isDisabled}>
|
||||
{' '}
|
||||
│{' '}
|
||||
</Text>
|
||||
<Box width={4}>
|
||||
<Text color={textColor()} dimColor={isDisabled}>
|
||||
{session.messageCount}
|
||||
</Text>
|
||||
</Box>
|
||||
<Text color={textColor(Colors.Gray)} dimColor={isDisabled}>
|
||||
{' '}
|
||||
│{' '}
|
||||
</Text>
|
||||
<Box width={4}>
|
||||
<Text color={textColor()} dimColor={isDisabled}>
|
||||
{formatRelativeTime(session.lastUpdated, 'short')}
|
||||
</Text>
|
||||
</Box>
|
||||
<Text color={textColor(Colors.Gray)} dimColor={isDisabled}>
|
||||
{' '}
|
||||
│{' '}
|
||||
</Text>
|
||||
<Box flexGrow={1}>
|
||||
<Text color={textColor(Colors.Comment)} dimColor={isDisabled}>
|
||||
{truncatedMessage}
|
||||
{additionalInfo && (
|
||||
<Text color={textColor(Colors.Gray)} dimColor bold={false}>
|
||||
{additionalInfo}
|
||||
</Text>
|
||||
)}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { Colors } from '../../colors.js';
|
||||
import type { SessionBrowserState } from '../SessionBrowser.js';
|
||||
import { SessionItem } from './SessionItem.js';
|
||||
import { SessionTableHeader } from './SessionTableHeader.js';
|
||||
import { NavigationHelp } from './NavigationHelp.js';
|
||||
|
||||
/**
|
||||
* Session list container component.
|
||||
*/
|
||||
export const SessionList = ({
|
||||
state,
|
||||
formatRelativeTime,
|
||||
}: {
|
||||
state: SessionBrowserState;
|
||||
formatRelativeTime: (dateString: string, style: 'short' | 'long') => string;
|
||||
}): React.JSX.Element => (
|
||||
<Box flexDirection="column">
|
||||
{/* Table Header */}
|
||||
<Box flexDirection="column">
|
||||
{!state.isSearchMode && <NavigationHelp />}
|
||||
<SessionTableHeader state={state} />
|
||||
</Box>
|
||||
|
||||
{state.visibleSessions.map((session) => (
|
||||
<SessionItem
|
||||
key={session.id}
|
||||
session={session}
|
||||
state={state}
|
||||
terminalWidth={state.terminalWidth}
|
||||
formatRelativeTime={formatRelativeTime}
|
||||
/>
|
||||
))}
|
||||
|
||||
<Text color={Colors.Gray}>
|
||||
{state.endIndex < state.totalSessions ? <>▼</> : <Text dimColor>▼</Text>}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { Colors } from '../../colors.js';
|
||||
import type { SessionBrowserState } from '../SessionBrowser.js';
|
||||
|
||||
/**
|
||||
* Table header component with column labels and scroll indicators.
|
||||
*/
|
||||
export const SessionTableHeader = ({
|
||||
state,
|
||||
}: {
|
||||
state: SessionBrowserState;
|
||||
}): React.JSX.Element => (
|
||||
<Box flexDirection="row" marginTop={1}>
|
||||
<Text>{state.scrollOffset > 0 ? <Text>▲ </Text> : ' '}</Text>
|
||||
|
||||
<Box width={5} flexShrink={0}>
|
||||
<Text color={Colors.Gray} bold>
|
||||
Index
|
||||
</Text>
|
||||
</Box>
|
||||
<Text color={Colors.Gray}> │ </Text>
|
||||
<Box width={4} flexShrink={0}>
|
||||
<Text color={Colors.Gray} bold>
|
||||
Msgs
|
||||
</Text>
|
||||
</Box>
|
||||
<Text color={Colors.Gray}> │ </Text>
|
||||
<Box width={4} flexShrink={0}>
|
||||
<Text color={Colors.Gray} bold>
|
||||
Age
|
||||
</Text>
|
||||
</Box>
|
||||
<Text color={Colors.Gray}> │ </Text>
|
||||
<Box flexShrink={0}>
|
||||
<Text color={Colors.Gray} bold>
|
||||
{state.searchQuery ? 'Match' : 'Name'}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`SessionBrowser List Components > MatchSnippetDisplay renders correctly with snippets 1`] = `
|
||||
"You: hello world !
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`SessionBrowser List Components > SessionItem renders correctly 1`] = `
|
||||
"❯ #1 │ 5 │ 10m │ Test Session
|
||||
ago
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`SessionBrowser List Components > SessionList renders correctly 1`] = `
|
||||
"Navigate: ↑/↓ Resume: Enter Search: / Delete: x Quit: q
|
||||
Sort: s Reverse: r First/Last: g/G
|
||||
|
||||
Index │ Msgs │ Age │ Name
|
||||
❯ #1 │ 5 │ 10m │ Test Session
|
||||
ago
|
||||
▼
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`SessionBrowser List Components > SessionTableHeader renders correctly 1`] = `
|
||||
"
|
||||
Index │ Msgs │ Age │ Name
|
||||
"
|
||||
`;
|
||||
Reference in New Issue
Block a user