fix(chat): restore desktop editor file-open in PendingChangesBar (#981)

* fix(chat): restore desktop editor file-open in PendingChangesBar (#979)

Commit d1553ba removed the runtime?.editor branch from handleOpenFile,
breaking file-click in VS Code and Electron desktop runtimes.

Restore the 3-branch logic: editor.openDiff(patch) → editor.openFile()
→ openContextDiff() web fallback. Display paths remain relative.

* fix(chat): route non-git changed files to file view in desktop

DiffView requires a git repository to display diffs. In desktop
(Electron/Tauri), runtime.editor is unavailable, so non-git files
fell through to openContextDiff which shows "Not a git repository".

Route non-git files to openContextFile instead, which works without
git. Git-tracked files continue to use openContextDiff.

* feat(chat): per-turn changes dropdown for non-git; self-sufficient git sync in bar

- PendingChangesBar seeds git store + listens to onGitRefreshHint; works without RightSidebarTabs (fixes VS Code where bar never rendered).
- Bar is git-only now; non-git shows per-turn dropdown at end of completed assistant turns.
- Dropdown uses base-ui Popover for collision-aware position; icon-only collapse with tooltips at narrow container widths.
- Extract shared helpers (changedFiles.ts), popover list (ChangedFilesList.tsx), styles (changedFilesPopover.ts).

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
jwcrystal
2026-04-22 11:31:21 +03:00
committed by GitHub
co-authored by Bohdan Triapitsyn
parent be65a9bd1f
commit 81591430a1
7 changed files with 487 additions and 340 deletions
@@ -0,0 +1,63 @@
import React from 'react';
import { FileTypeIcon } from '@/components/icons/FileTypeIcon';
import { type ChangedFileEntry, getDisplayPath, getFileStats } from './changedFiles';
interface ChangedFilesListProps {
files: ChangedFileEntry[];
currentDirectory: string;
onOpenFile: (file: ChangedFileEntry) => void;
}
export const ChangedFilesList: React.FC<ChangedFilesListProps> = ({ files, currentDirectory, onOpenFile }) => {
return (
<>
<div className="flex items-center gap-1.5 px-2 py-1 typography-ui-label font-medium text-muted-foreground">
<span>Changed files</span>
<span className="typography-meta tabular-nums">{files.length}</span>
</div>
<div className="max-h-[260px] overflow-y-auto">
{files.map((file, index) => {
const { fileName, dirPart } = getDisplayPath(file, currentDirectory);
const stats = getFileStats(file);
return (
<button
key={`${file.path}:${index}`}
type="button"
className="relative flex w-full cursor-pointer items-center gap-2 rounded-lg px-2 py-1 typography-ui-label outline-hidden select-none text-left hover:bg-interactive-hover"
title={`Open ${file.path}`}
onClick={() => onOpenFile(file)}
>
<FileTypeIcon filePath={file.path} className="h-3.5 w-3.5 flex-shrink-0" />
<span className="min-w-0 flex-1 flex items-baseline overflow-hidden" title={file.path}>
{dirPart ? (
<>
<span
className="min-w-0 truncate text-muted-foreground"
style={{ direction: 'rtl', textAlign: 'left' }}
>
{dirPart}
</span>
<span className="flex-shrink-0">
<span className="text-muted-foreground">/</span>
<span className="text-foreground">{fileName}</span>
</span>
</>
) : (
<span className="truncate text-foreground">{fileName}</span>
)}
</span>
{(stats.additions > 0 || stats.deletions > 0) ? (
<span className="flex-shrink-0 inline-flex items-baseline gap-1 text-[0.75rem] tabular-nums">
{stats.additions > 0 ? <span style={{ color: 'var(--status-success)' }}>+{stats.additions}</span> : null}
{stats.deletions > 0 ? <span style={{ color: 'var(--status-error)' }}>-{stats.deletions}</span> : null}
</span>
) : null}
</button>
);
})}
</div>
</>
);
};