fix(cli): render full resume preview history (#5565)
Some checks are pending
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Lint (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Blocked by required conditions
Qwen Code CI / Integration Tests (CLI, No Sandbox) (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run

Co-authored-by: cyphercodes <cyphercodes@users.noreply.github.com>
This commit is contained in:
Rayan Salhab 2026-06-21 21:18:23 +03:00 committed by GitHub
parent e88356c6b7
commit b6988325e9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 104 additions and 37 deletions

View file

@ -35,7 +35,11 @@ function mockService(resolved: unknown) {
} as never;
}
function fakeResumedData() {
function fakeResumedData(
assistantParts: Array<{ text: string; thought?: boolean }> = [
{ text: 'Hi from assistant REPLY-MARKER' },
],
) {
return {
conversation: {
sessionId: 's1',
@ -66,7 +70,7 @@ function fakeResumedData() {
version: 'test',
message: {
role: 'model',
parts: [{ text: 'Hi from assistant REPLY-MARKER' }],
parts: assistantParts,
},
},
],
@ -112,6 +116,39 @@ describe('SessionPreview', () => {
expect(frame).toContain('REPLY-MARKER');
});
it('renders full resumed thinking content after load', async () => {
const thinkingText = Array.from(
{ length: 40 },
(_, index) => `RESUMED-THINKING-LINE-${index + 1}`,
).join('\n');
const svc = mockService(
fakeResumedData([
{ text: thinkingText, thought: true },
{ text: 'FINAL-ANSWER-MARKER' },
]),
);
const { lastFrame } = render(
<KeypressProvider kittyProtocolEnabled={false}>
<SessionPreview
sessionService={svc}
sessionId="s1"
sessionTitle="My session"
onExit={vi.fn()}
onResume={vi.fn()}
/>
</KeypressProvider>,
);
await wait(100);
const frame = lastFrame() ?? '';
expect(frame).toContain('Thinking…');
expect(frame).toContain('RESUMED-THINKING-LINE-1');
expect(frame).toContain('RESUMED-THINKING-LINE-40');
expect(frame).toContain('FINAL-ANSWER-MARKER');
expect(frame.indexOf('My session')).toBeLessThan(
frame.indexOf('RESUMED-THINKING-LINE-1'),
);
});
it('renders footer metadata (messageCount · time · branch)', async () => {
const svc = mockService(fakeResumedData());
const { lastFrame } = render(

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { Box, Text } from 'ink';
import { Box, Static, Text } from 'ink';
import { useEffect, useMemo, useState } from 'react';
import type {
ResumedSessionData,
@ -126,56 +126,86 @@ export function SessionPreview(props: SessionPreviewProps) {
}
const metaLine = metaParts.join(' · ');
const header = (
<Box key="header" paddingX={1}>
<Text bold color={theme.text.primary}>
{sessionTitle ?? t('Session Preview')}
</Text>
</Box>
);
const topSeparator = (
<Box key="top-separator">
<Text color={theme.border.default}>{'─'.repeat(separatorWidth)}</Text>
</Box>
);
const footerSeparator = (
<Box key="footer-separator">
<Text color={theme.border.default}>{'─'.repeat(separatorWidth)}</Text>
</Box>
);
const meta = metaLine ? (
<Box key="meta" paddingX={1}>
<Text color={theme.text.secondary}>{metaLine}</Text>
</Box>
) : null;
const footer = (
<Box key="footer" paddingX={1}>
<Text color={theme.text.secondary}>
{t('Enter to resume · Esc to back')}
</Text>
</Box>
);
if (data && !error) {
return (
<Box flexDirection="column" width={boxWidth}>
<Static
key={sessionId}
items={[
header,
topSeparator,
...items.map((item) => (
<HistoryItemDisplay
key={item.id}
item={item}
terminalWidth={boxWidth}
isPending={false}
/>
)),
footerSeparator,
...(meta ? [meta] : []),
footer,
]}
>
{(item) => item}
</Static>
</Box>
);
}
return (
<Box flexDirection="column" width={boxWidth}>
{/* Header */}
<Box paddingX={1}>
<Text bold color={theme.text.primary}>
{sessionTitle ?? t('Session Preview')}
</Text>
</Box>
<Box>
<Text color={theme.border.default}>{'─'.repeat(separatorWidth)}</Text>
</Box>
{header}
{topSeparator}
{/* Body: render all items, let the terminal's scrollback own overflow. */}
{/* Body */}
{error ? (
<Box paddingY={1} justifyContent="center">
<Text color={theme.status.error}>{error}</Text>
</Box>
) : !data ? (
) : (
<Box paddingY={1} justifyContent="center">
<Text color={theme.text.secondary}>
{t('Loading session preview...')}
</Text>
</Box>
) : (
<Box flexDirection="column">
{items.map((item) => (
<HistoryItemDisplay
key={item.id}
item={item}
terminalWidth={boxWidth}
isPending={false}
/>
))}
</Box>
)}
{/* Footer */}
<Box>
<Text color={theme.border.default}>{'─'.repeat(separatorWidth)}</Text>
</Box>
{metaLine && (
<Box paddingX={1}>
<Text color={theme.text.secondary}>{metaLine}</Text>
</Box>
)}
<Box paddingX={1}>
<Text color={theme.text.secondary}>
{t('Enter to resume · Esc to back')}
</Text>
</Box>
{footerSeparator}
{meta}
{footer}
</Box>
);
}