feat: optimize file content resolution and enhance loading state handling

- Added a fast path in FileContentResolver to skip history lookup for files created by the agent with empty original content.
- Improved loading state in FileSectionDiff to display a loading message and snippet preview when applicable.
- Adjusted header content class names in ClaudeLogsSection and button styles in CollapsibleTeamSection for better UI consistency.
- Refactored ContinuousScrollView to streamline rendering logic for file sections based on content availability.
This commit is contained in:
iliya 2026-03-15 08:24:00 +02:00
parent 05457aca0d
commit b886b6bf83
5 changed files with 48 additions and 26 deletions

View file

@ -74,6 +74,19 @@ export class FileContentResolver {
logger.debug(`Файл недоступен на диске: ${filePath}`); logger.debug(`Файл недоступен на диске: ${filePath}`);
} }
// Fast path: if the agent created the file and it still exists on disk,
// the original content is definitely empty, so skip expensive history lookup.
const hasWriteNew = snippets.some((s) => !s.isError && s.type === 'write-new');
if (hasWriteNew && currentContent !== null) {
const result = {
original: '',
modified: currentContent,
source: 'snippet-reconstruction' as const,
};
this.cacheResult(cacheKey, result);
return result;
}
// Strategy 1: Try file-history backup // Strategy 1: Try file-history backup
const historyResult = await this.tryFileHistoryBackup(teamName, memberName, filePath); const historyResult = await this.tryFileHistoryBackup(teamName, memberName, filePath);
if (historyResult) { if (historyResult) {

View file

@ -123,7 +123,7 @@ export const ClaudeLogsSection = ({
</Tooltip> </Tooltip>
) : undefined ) : undefined
} }
headerContentClassName={isSidebar ? 'flex-wrap items-center gap-y-1 py-1 pr-3' : 'pr-3'} headerContentClassName={isSidebar ? 'flex-wrap items-center gap-y-1 py-1 pr-1' : 'pr-1'}
headerExtra={sectionHeaderExtra} headerExtra={sectionHeaderExtra}
defaultOpen={false} defaultOpen={false}
contentClassName="pt-0 [overflow-anchor:none]" contentClassName="pt-0 [overflow-anchor:none]"

View file

@ -85,7 +85,7 @@ export const CollapsibleTeamSection = ({
> >
<button <button
type="button" type="button"
className={`absolute inset-0 z-0 cursor-pointer transition-colors ${isOpen ? 'rounded-t-md bg-[var(--color-section-bg-open)] hover:bg-[var(--color-section-hover-open)]' : 'rounded-md bg-[var(--color-section-bg)] hover:bg-[var(--color-section-hover)]'}`} className={`absolute inset-0 z-0 cursor-pointer transition-colors ${isOpen ? 'rounded-t-xl bg-[var(--color-section-bg-open)] hover:bg-[var(--color-section-hover-open)]' : 'rounded-xl bg-[var(--color-section-bg)] hover:bg-[var(--color-section-hover)]'}`}
onClick={() => onClick={() =>
setOpen((prev) => { setOpen((prev) => {
const next = !prev; const next = !prev;

View file

@ -12,7 +12,6 @@ import {
} from './CodeMirrorDiffUtils'; } from './CodeMirrorDiffUtils';
import { FileSectionDiff } from './FileSectionDiff'; import { FileSectionDiff } from './FileSectionDiff';
import { FileSectionHeader } from './FileSectionHeader'; import { FileSectionHeader } from './FileSectionHeader';
import { FileSectionPlaceholder } from './FileSectionPlaceholder';
import type { EditorView } from '@codemirror/view'; import type { EditorView } from '@codemirror/view';
import type { FileChangeWithContent, HunkDecision } from '@shared/types'; import type { FileChangeWithContent, HunkDecision } from '@shared/types';
@ -256,28 +255,25 @@ export const ContinuousScrollView = ({
onRestoreMissingFile={onRestoreMissingFile} onRestoreMissingFile={onRestoreMissingFile}
/> />
{!isCollapsed && {!isCollapsed && (
(hasContent ? ( <FileSectionDiff
<FileSectionDiff file={file}
file={file} fileContent={content}
fileContent={content} isLoading={!hasContent}
isLoading={false} collapseUnchanged={collapseUnchanged}
collapseUnchanged={collapseUnchanged} onHunkAccepted={onHunkAccepted}
onHunkAccepted={onHunkAccepted} onHunkRejected={onHunkRejected}
onHunkRejected={onHunkRejected} onFullyViewed={onFullyViewed}
onFullyViewed={onFullyViewed} onContentChanged={onContentChanged}
onContentChanged={onContentChanged} onEditorViewReady={handleEditorViewReady}
onEditorViewReady={handleEditorViewReady} discardCounter={discardCounters[filePath] ?? 0}
discardCounter={discardCounters[filePath] ?? 0} autoViewed={autoViewed}
autoViewed={autoViewed} isViewed={isViewed}
isViewed={isViewed} onSelectionChange={onSelectionChange}
onSelectionChange={onSelectionChange} globalHunkOffset={globalHunkOffsets?.[filePath] ?? 0}
globalHunkOffset={globalHunkOffsets?.[filePath] ?? 0} totalReviewHunks={totalReviewHunks}
totalReviewHunks={totalReviewHunks} />
/> )}
) : (
<FileSectionPlaceholder fileName={file.relativePath} />
))}
</div> </div>
); );
})} })}

View file

@ -80,7 +80,20 @@ export const FileSectionDiff = ({
// Loading state // Loading state
if (isLoading) { if (isLoading) {
return <FileSectionPlaceholder fileName={file.relativePath} />; const hasSnippetPreview = file.snippets.some((snippet) => !snippet.isError);
if (!hasSnippetPreview) {
return <FileSectionPlaceholder fileName={file.relativePath} />;
}
return (
<div className="overflow-auto">
<div className="bg-surface-raised/40 border-b border-border px-4 py-2 text-xs text-text-muted">
Loading full diff...
</div>
<ReviewDiffContent file={file} />
<div ref={sentinelRef} className="h-1 shrink-0" />
</div>
);
} }
// Resolve modified content: prefer full content, fall back to write-type snippet // Resolve modified content: prefer full content, fall back to write-type snippet