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
@@ -6,6 +6,7 @@ import ToolPart from './parts/ToolPart';
import AssistantTextPart from './parts/AssistantTextPart';
import ReasoningPart from './parts/ReasoningPart';
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';
@@ -1593,25 +1594,41 @@ const AssistantMessageBody: React.FC<Omit<MessageBodyProps, 'isUser'>> = ({
</div>
<MessageFilesDisplay files={parts} onShowPopup={onShowPopup} />
{shouldShowFooter && (
<div className="mt-2 mb-1 flex items-center justify-start gap-1.5">
<div
className="mt-2 mb-1 flex items-center justify-start gap-1.5"
style={{ containerType: 'inline-size', containerName: 'message-footer' }}
>
<div className="flex items-center gap-1.5">
{footerButtons}
</div>
<div className="flex items-center gap-1.5">
{turnDurationText ? (
<span className="text-sm text-muted-foreground/60 tabular-nums flex items-center gap-1">
<RiHourglassLine className="h-3.5 w-3.5" />
{turnDurationText}
</span>
<Tooltip delayDuration={300}>
<TooltipTrigger asChild>
<span className="text-sm text-muted-foreground/60 tabular-nums flex items-center gap-1">
<RiHourglassLine className="h-3.5 w-3.5" />
<span className="message-footer__label">{turnDurationText}</span>
</span>
</TooltipTrigger>
<TooltipContent>{turnDurationText}</TooltipContent>
</Tooltip>
) : null}
{footerTimestamp ? (
<span
className={footerTimestampClassName}
aria-label={`Message time: ${footerTimestamp}`}
>
<RiTimeLine className="h-3.5 w-3.5" />
{footerTimestamp}
</span>
<Tooltip delayDuration={300}>
<TooltipTrigger asChild>
<span
className={footerTimestampClassName}
aria-label={`Message time: ${footerTimestamp}`}
>
<RiTimeLine className="h-3.5 w-3.5" />
<span className="message-footer__label">{footerTimestamp}</span>
</span>
</TooltipTrigger>
<TooltipContent>{footerTimestamp}</TooltipContent>
</Tooltip>
) : null}
{isLastAssistantInTurn && hasStopFinish ? (
<TurnChangedFilesDropdown activityParts={turnGroupingContext?.activityParts} />
) : null}
</div>
</div>