diff --git a/packages/ui/src/components/chat/MessageList.tsx b/packages/ui/src/components/chat/MessageList.tsx index e8762c55..58c95315 100644 --- a/packages/ui/src/components/chat/MessageList.tsx +++ b/packages/ui/src/components/chat/MessageList.tsx @@ -685,10 +685,11 @@ const TurnBlock = React.memo(({ hasTools: turn.hasTools, hasReasoning: turn.hasReasoning, diffStats: turn.diffStats, + changedFiles: turn.changedFiles, userMessageCreatedAt: typeof userCreatedAt === 'number' ? userCreatedAt : undefined, userMessageVariant, }; - }, [turn.diffStats, turn.hasReasoning, turn.hasTools, turn.headerMessageId, turn.summaryText, turn.turnId, turn.userMessage.info, visibleActivityParts, visibleActivitySegments]); + }, [turn.changedFiles, turn.diffStats, turn.hasReasoning, turn.hasTools, turn.headerMessageId, turn.summaryText, turn.turnId, turn.userMessage.info, visibleActivityParts, visibleActivitySegments]); const renderMessage = React.useCallback( (message: ChatMessageEntry) => { @@ -736,6 +737,7 @@ const TurnBlock = React.memo(({ activityGroupSegments: turnGroupingContextBase.activityGroupSegments, headerMessageId: turnGroupingContextBase.headerMessageId, diffStats: turnGroupingContextBase.diffStats, + changedFiles: turnGroupingContextBase.changedFiles, userMessageCreatedAt: turnGroupingContextBase.userMessageCreatedAt, userMessageVariant: turnGroupingContextBase.userMessageVariant, isGroupExpanded: turnUiState.isExpanded, @@ -1114,6 +1116,7 @@ const MessageList = React.forwardRef(({ const stickyUserHeader = useUIStore(state => state.stickyUserHeader); const chatRenderMode = useUIStore((state) => state.chatRenderMode); const activityRenderMode = useUIStore((state) => state.activityRenderMode); + const showTurnChangedFiles = useUIStore((state) => state.showTurnChangedFiles); const defaultActivityExpanded = activityRenderMode === 'summary'; const [turnUiStates, setTurnUiStates] = React.useState>(() => new Map()); const userAnimationRef = React.useRef<{ @@ -1212,6 +1215,7 @@ const MessageList = React.forwardRef(({ const { projection, staticTurns, streamingTurn } = useTurnRecords(displayMessages, { sessionKey, showTextJustificationActivity: chatRenderMode === 'sorted', + showTurnChangedFiles, }); const hasUngroupedStaticEntries = projection.ungroupedMessageIds.size > 0; const staticEntryMessages = hasUngroupedStaticEntries ? displayMessages : EMPTY_STATIC_ENTRY_MESSAGES; diff --git a/packages/ui/src/components/chat/hooks/useTurnRecords.ts b/packages/ui/src/components/chat/hooks/useTurnRecords.ts index 2879107b..9538db37 100644 --- a/packages/ui/src/components/chat/hooks/useTurnRecords.ts +++ b/packages/ui/src/components/chat/hooks/useTurnRecords.ts @@ -6,6 +6,7 @@ import { streamPerfMeasure } from '@/stores/utils/streamDebug'; interface UseTurnRecordsOptions { sessionKey?: string; showTextJustificationActivity: boolean; + showTurnChangedFiles: boolean; } export interface TurnRecordsResult { @@ -23,13 +24,16 @@ export const useTurnRecords = ( const streamingTurnRef = React.useRef(undefined); const previousSessionKeyRef = React.useRef(options.sessionKey); const previousShowTextJustificationActivityRef = React.useRef(options.showTextJustificationActivity); + const previousShowTurnChangedFilesRef = React.useRef(options.showTurnChangedFiles); if ( previousSessionKeyRef.current !== options.sessionKey || previousShowTextJustificationActivityRef.current !== options.showTextJustificationActivity + || previousShowTurnChangedFilesRef.current !== options.showTurnChangedFiles ) { previousSessionKeyRef.current = options.sessionKey; previousShowTextJustificationActivityRef.current = options.showTextJustificationActivity; + previousShowTurnChangedFilesRef.current = options.showTurnChangedFiles; previousProjectionRef.current = null; staticTurnsRef.current = []; streamingTurnRef.current = undefined; @@ -39,18 +43,19 @@ export const useTurnRecords = ( previousProjectionRef.current = null; staticTurnsRef.current = []; streamingTurnRef.current = undefined; - }, [options.sessionKey, options.showTextJustificationActivity]); + }, [options.sessionKey, options.showTextJustificationActivity, options.showTurnChangedFiles]); const projection = React.useMemo(() => { return streamPerfMeasure('ui.turns.projection_ms', () => { const nextProjection = projectTurnRecords(messages, { previousProjection: previousProjectionRef.current, showTextJustificationActivity: options.showTextJustificationActivity, + showTurnChangedFiles: options.showTurnChangedFiles, }); previousProjectionRef.current = nextProjection; return nextProjection; }); - }, [messages, options.showTextJustificationActivity]); + }, [messages, options.showTextJustificationActivity, options.showTurnChangedFiles]); const staticTurns = React.useMemo(() => { const nextStatic = projection.turns.length <= 1 diff --git a/packages/ui/src/components/chat/lib/turns/projectTurnRecords.ts b/packages/ui/src/components/chat/lib/turns/projectTurnRecords.ts index 0fee35eb..d9eb27dc 100644 --- a/packages/ui/src/components/chat/lib/turns/projectTurnRecords.ts +++ b/packages/ui/src/components/chat/lib/turns/projectTurnRecords.ts @@ -1,6 +1,6 @@ import { projectTurnActivity } from './projectTurnActivity'; import { projectTurnIndexes } from './projectTurnIndexes'; -import { projectTurnDiffStats, projectTurnSummary } from './projectTurnSummary'; +import { projectTurnChangedFiles, projectTurnDiffStats, projectTurnSummary } from './projectTurnSummary'; import type { ChatMessageEntry, TurnMessageRecord, @@ -83,11 +83,13 @@ const buildTurnStreamState = (userMessage: ChatMessageEntry, assistantMessages: interface ProjectTurnRecordsOptions { previousProjection?: TurnProjectionResult | null; showTextJustificationActivity: boolean; + showTurnChangedFiles: boolean; } const DEFAULT_OPTIONS: ProjectTurnRecordsOptions = { previousProjection: null, showTextJustificationActivity: false, + showTurnChangedFiles: false, }; const areSameMessageRefs = (left: ChatMessageEntry[], right: ChatMessageEntry[]): boolean => { @@ -180,6 +182,7 @@ export const projectTurnRecords = ( hasTools: false, hasReasoning: false, diffStats: undefined, + changedFiles: undefined, stream: { isStreaming: false, isRetrying: false, @@ -215,6 +218,9 @@ export const projectTurnRecords = ( turn.summary = projectTurnSummary(turn.assistantMessages); turn.summaryText = turn.summary.text ?? getUserSummaryBody(turn.userMessage); turn.diffStats = projectTurnDiffStats(turn.userMessage); + turn.changedFiles = effectiveOptions.showTurnChangedFiles + ? projectTurnChangedFiles(turn.userMessage) + : undefined; const activity = projectTurnActivity({ turnId: turn.turnId, diff --git a/packages/ui/src/components/chat/lib/turns/projectTurnSummary.ts b/packages/ui/src/components/chat/lib/turns/projectTurnSummary.ts index f2128164..b1117728 100644 --- a/packages/ui/src/components/chat/lib/turns/projectTurnSummary.ts +++ b/packages/ui/src/components/chat/lib/turns/projectTurnSummary.ts @@ -1,6 +1,7 @@ -import type { ChatMessageEntry, TurnDiffStats, TurnSummaryRecord } from './types'; +import type { ChatMessageEntry, TurnChangedFile, TurnDiffStats, TurnSummaryRecord } from './types'; interface SummaryDiff { + file?: string | null; additions?: number | null; deletions?: number | null; } @@ -102,3 +103,31 @@ export const projectTurnDiffStats = (userMessage: ChatMessageEntry): TurnDiffSta files, }; }; + +export const projectTurnChangedFiles = (userMessage: ChatMessageEntry): TurnChangedFile[] | undefined => { + const summary = (userMessage.info as { summary?: UserSummaryPayload | null }).summary; + const diffs = summary?.diffs; + if (!Array.isArray(diffs) || diffs.length === 0) { + return undefined; + } + + const files = diffs + .map((diff) => { + if (!diff || typeof diff.file !== 'string' || diff.file.trim().length === 0) { + return null; + } + const additions = typeof diff.additions === 'number' ? diff.additions : 0; + const deletions = typeof diff.deletions === 'number' ? diff.deletions : 0; + if (additions === 0 && deletions === 0) { + return null; + } + return { + file: diff.file, + additions, + deletions, + }; + }) + .filter((file): file is TurnChangedFile => file !== null); + + return files.length > 0 ? files : undefined; +}; diff --git a/packages/ui/src/components/chat/lib/turns/types.ts b/packages/ui/src/components/chat/lib/turns/types.ts index e2024098..f7306969 100644 --- a/packages/ui/src/components/chat/lib/turns/types.ts +++ b/packages/ui/src/components/chat/lib/turns/types.ts @@ -34,6 +34,12 @@ export interface TurnDiffStats { files: number; } +export interface TurnChangedFile { + file: string; + additions: number; + deletions: number; +} + export interface TurnActivityGroup { id: string; anchorMessageId: string; @@ -70,6 +76,7 @@ export interface TurnRecord { hasTools: boolean; hasReasoning: boolean; diffStats?: TurnDiffStats; + changedFiles?: TurnChangedFile[]; stream: TurnStreamState; startedAt?: number; completedAt?: number; @@ -115,6 +122,7 @@ export interface TurnGroupingContext { hasTools: boolean; hasReasoning: boolean; diffStats?: TurnDiffStats; + changedFiles?: TurnChangedFile[]; userMessageCreatedAt?: number; userMessageVariant?: string; isWorking: boolean; diff --git a/packages/ui/src/components/chat/message/MessageBody.tsx b/packages/ui/src/components/chat/message/MessageBody.tsx index 7f93bbf6..79ae9f6c 100644 --- a/packages/ui/src/components/chat/message/MessageBody.tsx +++ b/packages/ui/src/components/chat/message/MessageBody.tsx @@ -9,7 +9,7 @@ import { MessageFilesDisplay } from '../FileAttachment'; import { TurnChangedFilesDropdown } from '../TurnChangedFilesDropdown'; import type { ToolPart as ToolPartType } from '@opencode-ai/sdk/v2'; import type { StreamPhase, ToolPopupContent, AgentMentionInfo } from './types'; -import type { TurnGroupingContext } from '../lib/turns/types'; +import type { TurnChangedFile, TurnGroupingContext } from '../lib/turns/types'; import { cn } from '@/lib/utils'; import { isEmptyTextPart, extractTextContent } from './partUtils'; import { FadeInOnReveal } from './FadeInOnReveal'; @@ -47,12 +47,50 @@ import { useEffectiveDirectory } from '@/hooks/useEffectiveDirectory'; import { useI18n } from '@/lib/i18n'; import { extractLoopbackUrls } from '@/lib/url'; import { useDeviceInfo } from '@/lib/device'; +import { FileTypeIcon } from '@/components/icons/FileTypeIcon'; const CONTAIN_LAYOUT_STYLE = { contain: 'layout' as const, transform: 'translateZ(0)' }; const MESSAGE_FOOTER_CONTAINER_STYLE = { containerType: 'inline-size' as const, containerName: 'message-footer' }; const INLINE_MESSAGE_ACTIONS_CLASS_NAME = 'mt-2 mb-1 flex items-center justify-start gap-1.5'; +const getDisplayFileName = (file: string): string => { + const normalized = file.replace(/\\/g, '/'); + const segments = normalized.split('/').filter(Boolean); + return segments.at(-1) ?? file; +}; + +const TurnChangedFilePills = React.memo(({ files }: { files?: TurnChangedFile[] }) => { + if (!files || files.length === 0) { + return null; + } + + return ( + <> + {files.map((file) => { + return ( + + + + + + {getDisplayFileName(file.file)} + + +{file.additions} + / + -{file.deletions} + + + + + {file.file} + + ); + })} + + ); +}); + type SubtaskPartLike = Part & { type: 'subtask'; description?: unknown; @@ -1918,43 +1956,44 @@ const AssistantMessageBody = React.memo(({ )} {shouldShowTurnFooter && (
{messageActionButtons} {finalTurnActionButtons}
-
- {turnDurationText ? ( - - - - - {turnDurationText} - - - {turnDurationText} - - ) : null} - {footerTimestamp ? ( - - - - - {footerTimestamp} - - - {footerTimestamp} - - ) : null} - {!isMiniChatSurface && isLastAssistantInTurn && hasStopFinish ? ( - - ) : null} -
+ {turnDurationText ? ( + + + + + {turnDurationText} + + + {turnDurationText} + + ) : null} + {footerTimestamp ? ( + + + + + {footerTimestamp} + + + {footerTimestamp} + + ) : null} + {!isMiniChatSurface && isLastAssistantInTurn && hasStopFinish ? ( + + ) : null} + {!isMiniChatSurface && isLastAssistantInTurn && hasStopFinish ? ( + + ) : null}
)} diff --git a/packages/ui/src/components/chat/message/renderCompare.ts b/packages/ui/src/components/chat/message/renderCompare.ts index 057f15c4..9101b59d 100644 --- a/packages/ui/src/components/chat/message/renderCompare.ts +++ b/packages/ui/src/components/chat/message/renderCompare.ts @@ -1,5 +1,5 @@ import type { Message, Part } from '@opencode-ai/sdk/v2'; -import type { TurnActivityGroup, TurnActivityRecord, TurnDiffStats, TurnGroupingContext } from '../lib/turns/types'; +import type { TurnActivityGroup, TurnActivityRecord, TurnChangedFile, TurnDiffStats, TurnGroupingContext } from '../lib/turns/types'; type MessageRecord = { info: Message; @@ -146,6 +146,24 @@ const areTurnDiffStatsEqual = (left?: TurnDiffStats, right?: TurnDiffStats): boo && left.files === right.files; }; +const areTurnChangedFilesEqual = (left?: TurnChangedFile[], right?: TurnChangedFile[]): boolean => { + if (left === right) return true; + if (!left || !right) return !left && !right; + if (left.length !== right.length) return false; + for (let index = 0; index < left.length; index += 1) { + const leftFile = left[index]; + const rightFile = right[index]; + if ( + leftFile.file !== rightFile.file + || leftFile.additions !== rightFile.additions + || leftFile.deletions !== rightFile.deletions + ) { + return false; + } + } + return true; +}; + const areTurnActivityRecordsEqual = (left: TurnActivityRecord, right: TurnActivityRecord): boolean => { return left.id === right.id && left.messageId === right.messageId @@ -301,5 +319,9 @@ export const areRelevantTurnGroupingContextsEqual = ( return false; } + if ((ownerRelevant || segmentsRelevant || left.isLastAssistantInTurn || right.isLastAssistantInTurn) && !areTurnChangedFilesEqual(left.changedFiles, right.changedFiles)) { + return false; + } + return true; }; diff --git a/packages/ui/src/components/sections/openchamber/OpenChamberPage.tsx b/packages/ui/src/components/sections/openchamber/OpenChamberPage.tsx index 7e998f5f..180cc7ea 100644 --- a/packages/ui/src/components/sections/openchamber/OpenChamberPage.tsx +++ b/packages/ui/src/components/sections/openchamber/OpenChamberPage.tsx @@ -142,7 +142,7 @@ const VisualSectionContent: React.FC = () => { // Chat section: User message rendering, Diff layout, Mobile status bar, Show reasoning traces, Queue mode, Persist draft const ChatSectionContent: React.FC = () => { - return ; + return ; }; // Sessions section: Default model & agent, Session retention diff --git a/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx b/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx index 07777071..d6d47509 100644 --- a/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx +++ b/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx @@ -247,7 +247,7 @@ const normalizeUserMessageRenderingMode = (mode: unknown): 'markdown' | 'plain' return mode === 'markdown' ? 'markdown' : 'plain'; }; -export type VisibleSetting = 'theme' | 'pwaInstallName' | 'pwaOrientation' | 'mobileKeyboardMode' | 'timeFormat' | 'weekStart' | 'fontSize' | 'terminalFontSize' | 'spacing' | 'inputBarOffset' | 'mermaidRendering' | 'userMessageRendering' | 'chatRenderMode' | 'messageTransport' | 'activityRenderMode' | 'stickyUserHeader' | 'wideChatLayout' | 'splitAssistantMessageActions' | 'diffLayout' | 'mobileStatusBar' | 'dotfiles' | 'fileViewerPreview' | 'reasoning' | 'showToolFileIcons' | 'expandedTools' | 'queueMode' | 'terminalQuickKeys' | 'persistDraft' | 'inputSpellcheck' | 'reportUsage'; +export type VisibleSetting = 'theme' | 'pwaInstallName' | 'pwaOrientation' | 'mobileKeyboardMode' | 'timeFormat' | 'weekStart' | 'fontSize' | 'terminalFontSize' | 'spacing' | 'inputBarOffset' | 'mermaidRendering' | 'userMessageRendering' | 'chatRenderMode' | 'messageTransport' | 'activityRenderMode' | 'stickyUserHeader' | 'wideChatLayout' | 'splitAssistantMessageActions' | 'diffLayout' | 'mobileStatusBar' | 'dotfiles' | 'fileViewerPreview' | 'reasoning' | 'showToolFileIcons' | 'showTurnChangedFiles' | 'expandedTools' | 'queueMode' | 'terminalQuickKeys' | 'persistDraft' | 'inputSpellcheck' | 'reportUsage'; interface OpenChamberVisualSettingsProps { /** Which settings to show. If undefined, shows all. */ @@ -305,6 +305,8 @@ export const OpenChamberVisualSettings: React.FC const setInputSpellcheckEnabled = useUIStore(state => state.setInputSpellcheckEnabled); const showToolFileIcons = useUIStore(state => state.showToolFileIcons); const setShowToolFileIcons = useUIStore(state => state.setShowToolFileIcons); + const showTurnChangedFiles = useUIStore(state => state.showTurnChangedFiles); + const setShowTurnChangedFiles = useUIStore(state => state.setShowTurnChangedFiles); const showExpandedBashTools = useUIStore(state => state.showExpandedBashTools); const setShowExpandedBashTools = useUIStore(state => state.setShowExpandedBashTools); const showExpandedEditTools = useUIStore(state => state.showExpandedEditTools); @@ -441,6 +443,11 @@ export const OpenChamberVisualSettings: React.FC void updateDesktopSettings({ showToolFileIcons: enabled }); }, [setShowToolFileIcons]); + const handleShowTurnChangedFilesChange = React.useCallback((enabled: boolean) => { + setShowTurnChangedFiles(enabled); + void updateDesktopSettings({ showTurnChangedFiles: enabled }); + }, [setShowTurnChangedFiles]); + const handleFileViewerPreviewChange = React.useCallback((enabled: boolean) => { setSettingsDefaultFileViewerPreview(enabled); void updateDesktopSettings({ defaultFileViewerPreview: enabled }); @@ -1587,7 +1594,7 @@ export const OpenChamberVisualSettings: React.FC )} - {(shouldShow('stickyUserHeader') || shouldShow('wideChatLayout') || shouldShow('splitAssistantMessageActions') || (shouldShow('mobileStatusBar') && isMobile) || shouldShow('dotfiles') || shouldShow('fileViewerPreview') || shouldShow('queueMode') || shouldShow('persistDraft') || shouldShow('showToolFileIcons') || (!isMobile && shouldShow('inputSpellcheck')) || shouldShow('reasoning')) && ( + {(shouldShow('stickyUserHeader') || shouldShow('wideChatLayout') || shouldShow('splitAssistantMessageActions') || (shouldShow('mobileStatusBar') && isMobile) || shouldShow('dotfiles') || shouldShow('fileViewerPreview') || shouldShow('queueMode') || shouldShow('persistDraft') || shouldShow('showToolFileIcons') || shouldShow('showTurnChangedFiles') || (!isMobile && shouldShow('inputSpellcheck')) || shouldShow('reasoning')) && (
{shouldShow('reasoning') && (
)} + {shouldShow('showTurnChangedFiles') && ( +
handleShowTurnChangedFilesChange(!showTurnChangedFiles)} + onKeyDown={(event) => { + if (event.key === ' ' || event.key === 'Enter') { + event.preventDefault(); + handleShowTurnChangedFilesChange(!showTurnChangedFiles); + } + }} + > + + {t('settings.openchamber.visual.field.showTurnChangedFiles')} +
+ )} + {shouldShow('mobileStatusBar') && isMobile && (
{ if (typeof settings.showToolFileIcons === 'boolean' && settings.showToolFileIcons !== store.showToolFileIcons) { store.setShowToolFileIcons(settings.showToolFileIcons); } + if (typeof settings.showTurnChangedFiles === 'boolean' && settings.showTurnChangedFiles !== store.showTurnChangedFiles) { + store.setShowTurnChangedFiles(settings.showTurnChangedFiles); + } if (typeof settings.showExpandedBashTools === 'boolean' && settings.showExpandedBashTools !== store.showExpandedBashTools) { store.setShowExpandedBashTools(settings.showExpandedBashTools); } @@ -957,6 +960,9 @@ const sanitizeWebSettings = (payload: unknown): DesktopSettings | null => { if (typeof candidate.showToolFileIcons === 'boolean') { result.showToolFileIcons = candidate.showToolFileIcons; } + if (typeof candidate.showTurnChangedFiles === 'boolean') { + result.showTurnChangedFiles = candidate.showTurnChangedFiles; + } if (typeof candidate.showExpandedBashTools === 'boolean') { result.showExpandedBashTools = candidate.showExpandedBashTools; } diff --git a/packages/ui/src/stores/useUIStore.ts b/packages/ui/src/stores/useUIStore.ts index 92e073ce..bc33f12c 100644 --- a/packages/ui/src/stores/useUIStore.ts +++ b/packages/ui/src/stores/useUIStore.ts @@ -605,6 +605,7 @@ interface UIStore { inputSpellcheckEnabled: boolean; wideChatLayoutEnabled: boolean; showToolFileIcons: boolean; + showTurnChangedFiles: boolean; showExpandedBashTools: boolean; showExpandedEditTools: boolean; timeFormatPreference: TimeFormatPreference; @@ -743,6 +744,7 @@ interface UIStore { setInputSpellcheckEnabled: (value: boolean) => void; setWideChatLayoutEnabled: (value: boolean) => void; setShowToolFileIcons: (value: boolean) => void; + setShowTurnChangedFiles: (value: boolean) => void; setShowExpandedBashTools: (value: boolean) => void; setShowExpandedEditTools: (value: boolean) => void; setTimeFormatPreference: (value: TimeFormatPreference) => void; @@ -876,6 +878,7 @@ export const useUIStore = create()( inputSpellcheckEnabled: false, wideChatLayoutEnabled: false, showToolFileIcons: true, + showTurnChangedFiles: false, showExpandedBashTools: false, showExpandedEditTools: false, timeFormatPreference: 'auto', @@ -1942,6 +1945,9 @@ export const useUIStore = create()( setShowToolFileIcons: (value) => { set({ showToolFileIcons: value }); }, + setShowTurnChangedFiles: (value) => { + set({ showTurnChangedFiles: value }); + }, setShowExpandedBashTools: (value) => { set({ showExpandedBashTools: value }); }, @@ -2183,6 +2189,7 @@ export const useUIStore = create()( inputSpellcheckEnabled: state.inputSpellcheckEnabled, wideChatLayoutEnabled: state.wideChatLayoutEnabled, showToolFileIcons: state.showToolFileIcons, + showTurnChangedFiles: state.showTurnChangedFiles, showExpandedBashTools: state.showExpandedBashTools, showExpandedEditTools: state.showExpandedEditTools, timeFormatPreference: state.timeFormatPreference, diff --git a/packages/web/server/lib/opencode/settings-helpers.js b/packages/web/server/lib/opencode/settings-helpers.js index 97d2fea9..57469b97 100644 --- a/packages/web/server/lib/opencode/settings-helpers.js +++ b/packages/web/server/lib/opencode/settings-helpers.js @@ -391,6 +391,9 @@ export const createSettingsHelpers = (dependencies) => { if (typeof candidate.showToolFileIcons === 'boolean') { result.showToolFileIcons = candidate.showToolFileIcons; } + if (typeof candidate.showTurnChangedFiles === 'boolean') { + result.showTurnChangedFiles = candidate.showTurnChangedFiles; + } if (typeof candidate.showExpandedBashTools === 'boolean') { result.showExpandedBashTools = candidate.showExpandedBashTools; }