From f2f230914d09ced6d74178048501d26659fc376c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E9=98=BF=E6=B1=9F=28Relakkes?= =?UTF-8?q?=29?= Date: Sat, 30 May 2026 18:15:00 +0800 Subject: [PATCH] feat(desktop): collapse long change-file lists in the turn card (show N more) Co-Authored-By: Claude Opus 4.8 (1M context) --- .../chat/CurrentTurnChangeCard.test.tsx | 46 +++++++++++++++++++ .../components/chat/CurrentTurnChangeCard.tsx | 32 ++++++++++++- desktop/src/i18n/locales/en.ts | 2 + desktop/src/i18n/locales/zh.ts | 2 + 4 files changed, 80 insertions(+), 2 deletions(-) diff --git a/desktop/src/components/chat/CurrentTurnChangeCard.test.tsx b/desktop/src/components/chat/CurrentTurnChangeCard.test.tsx index c1526f77..34b640ce 100644 --- a/desktop/src/components/chat/CurrentTurnChangeCard.test.tsx +++ b/desktop/src/components/chat/CurrentTurnChangeCard.test.tsx @@ -251,3 +251,49 @@ describe('CurrentTurnChangeCard – open-with buttons', () => { }) }) }) + +describe('CurrentTurnChangeCard – collapse long file lists', () => { + beforeEach(() => { + vi.clearAllMocks() + ensureTargetsMock.mockResolvedValue(undefined) + openPreviewSpy.mockResolvedValue(undefined) + }) + + function makeFiles(count: number): string[] { + return Array.from({ length: count }, (_, i) => `/w/proj/src/file${i + 1}.ts`) + } + + it('does NOT render a show-more toggle with ≤5 files', () => { + renderCard(makeFiles(5)) + expect(screen.getAllByRole('button', { name: /turnChangesShowDiffAria/ })).toHaveLength(5) + expect(screen.queryByText('chat.turnChangesShowMore')).not.toBeInTheDocument() + expect(screen.queryByText('chat.turnChangesShowLess')).not.toBeInTheDocument() + }) + + it('with 8 files shows only 5 rows + a "show more" toggle (remaining = 3)', () => { + renderCard(makeFiles(8)) + // only the first 5 diff-toggle rows are rendered + expect(screen.getAllByRole('button', { name: /turnChangesShowDiffAria/ })).toHaveLength(5) + // the show-more toggle is present (identity-mock key). The real key carries the + // remaining count via '{count}'; with the placeholder-bearing real string this + // renders as "再显示 3 个文件" (8 - COLLAPSED_COUNT(5) = 3). + expect(screen.getByText('chat.turnChangesShowMore')).toBeInTheDocument() + // …and it is the only toggle (no "show less" while collapsed) + expect(screen.queryByText('chat.turnChangesShowLess')).not.toBeInTheDocument() + }) + + it('clicking "show more" reveals all 8 rows and shows "show less"; clicking again re-collapses', () => { + renderCard(makeFiles(8)) + const showMore = screen.getByText('chat.turnChangesShowMore') + + fireEvent.click(showMore) + expect(screen.getAllByRole('button', { name: /turnChangesShowDiffAria/ })).toHaveLength(8) + const showLess = screen.getByText('chat.turnChangesShowLess') + expect(showLess).toBeInTheDocument() + expect(screen.queryByText('chat.turnChangesShowMore')).not.toBeInTheDocument() + + fireEvent.click(showLess) + expect(screen.getAllByRole('button', { name: /turnChangesShowDiffAria/ })).toHaveLength(5) + expect(screen.getByText('chat.turnChangesShowMore')).toBeInTheDocument() + }) +}) diff --git a/desktop/src/components/chat/CurrentTurnChangeCard.tsx b/desktop/src/components/chat/CurrentTurnChangeCard.tsx index 64f403e0..5c2f251a 100644 --- a/desktop/src/components/chat/CurrentTurnChangeCard.tsx +++ b/desktop/src/components/chat/CurrentTurnChangeCard.tsx @@ -1,6 +1,6 @@ import { useCallback, useMemo, useState } from 'react' import type { MouseEvent as ReactMouseEvent } from 'react' -import { ChevronDown } from 'lucide-react' +import { ChevronDown, ChevronUp } from 'lucide-react' import { sessionsApi, type SessionTurnCheckpoint } from '../../api/sessions' import { useTranslation, type TranslationKey } from '../../i18n' import { WorkspaceDiffSurface } from '../workspace/WorkspaceCodeSurface' @@ -34,6 +34,8 @@ type ChangedFileEntry = { displayPath: string } +const COLLAPSED_COUNT = 5 + export function CurrentTurnChangeCard({ sessionId, targetUserMessageId, @@ -48,6 +50,7 @@ export function CurrentTurnChangeCard({ const [expandedPath, setExpandedPath] = useState(null) const [diffByPath, setDiffByPath] = useState>({}) const [openWith, setOpenWith] = useState<{ items: OpenWithItem[]; anchor: DOMRect } | null>(null) + const [showAllFiles, setShowAllFiles] = useState(false) const files = useMemo( () => checkpoint.code.filesChanged.map((filePath) => ({ @@ -57,6 +60,11 @@ export function CurrentTurnChangeCard({ [checkpoint.code.filesChanged, workDir], ) + const canCollapse = files.length > COLLAPSED_COUNT + const visibleFiles = canCollapse && !showAllFiles + ? files.slice(0, COLLAPSED_COUNT) + : files + const toggleDiff = useCallback((fileEntry: ChangedFileEntry) => { const nextExpandedPath = expandedPath === fileEntry.apiPath ? null : fileEntry.apiPath setExpandedPath(nextExpandedPath) @@ -171,7 +179,7 @@ export function CurrentTurnChangeCard({
- {files.map((fileEntry) => { + {visibleFiles.map((fileEntry) => { const isExpanded = expandedPath === fileEntry.apiPath const diffState = diffByPath[fileEntry.apiPath] const fileName = fileEntry.displayPath.split('/').pop() || fileEntry.displayPath @@ -238,6 +246,26 @@ export function CurrentTurnChangeCard({ })}
+ {canCollapse && ( + + )} + {error && (
{error} diff --git a/desktop/src/i18n/locales/en.ts b/desktop/src/i18n/locales/en.ts index 27c98101..9a47c71d 100644 --- a/desktop/src/i18n/locales/en.ts +++ b/desktop/src/i18n/locales/en.ts @@ -1213,6 +1213,8 @@ export const en = { 'chat.turnChangesHideDiffAria': 'Hide diff for {path}', 'chat.turnChangesDiffLoading': 'Loading diff...', 'chat.turnChangesDiffUnavailable': 'Diff unavailable.', + 'chat.turnChangesShowMore': 'Show {count} more files', + 'chat.turnChangesShowLess': 'Show less', // ─── Streaming Indicator ────────────────────────────────────── 'streaming.thinking': 'Thinking', diff --git a/desktop/src/i18n/locales/zh.ts b/desktop/src/i18n/locales/zh.ts index 97d0ab2e..8fff7489 100644 --- a/desktop/src/i18n/locales/zh.ts +++ b/desktop/src/i18n/locales/zh.ts @@ -1215,6 +1215,8 @@ export const zh: Record = { 'chat.turnChangesHideDiffAria': '收起 {path} 的 diff', 'chat.turnChangesDiffLoading': '正在加载 diff...', 'chat.turnChangesDiffUnavailable': 'Diff 不可用。', + 'chat.turnChangesShowMore': '再显示 {count} 个文件', + 'chat.turnChangesShowLess': '收起', // ─── Streaming Indicator ────────────────────────────────────── 'streaming.thinking': '思考中',