Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 59 additions & 36 deletions apps/differ/src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@
// ==========================================================================

type DiffMode = 'all' | 'staged' | 'branch' | 'commit' | 'stack';
type DiffViewerHandle = {
flushCommentEditors: () => Promise<boolean>;
};

let diffMode = $state<DiffMode>('all');
let diffSpec = $state<DiffSpec>(commands.specUncommitted());
Expand All @@ -122,6 +125,7 @@
let loading = $state(true);
let loadingFile = $state<string | null>(null);
let error = $state<string | null>(null);
let diffViewer = $state<DiffViewerHandle | null>(null);

let localComments = $state<Comment[]>([]);
let copiedFeedback = $state(false);
Expand Down Expand Up @@ -255,7 +259,21 @@
// Diff mode switching
// ==========================================================================

function setMode(mode: DiffMode, commit?: CommitInfo) {
async function flushCommentEditorsForDiffChange(): Promise<boolean> {
return (await diffViewer?.flushCommentEditors()) ?? true;
}

function resetDiffState() {
files = [];
diffCache = new Map();
selectedFile = null;
localComments = [];
error = null;
}

async function setMode(mode: DiffMode, commit?: CommitInfo): Promise<boolean> {
if (!(await flushCommentEditorsForDiffChange())) return false;

showCommitPicker = false;
showStackPicker = false;
diffMode = mode;
Expand Down Expand Up @@ -299,26 +317,22 @@
break;
}

files = [];
diffCache = new Map();
selectedFile = null;
localComments = [];
error = null;
resetDiffState();
loadDiff();
return true;
}

function selectCommitBySha(sha: string) {
async function selectCommitBySha(sha: string): Promise<boolean> {
if (!(await flushCommentEditorsForDiffChange())) return false;

diffMode = 'commit';
diffSpec = commands.specCommit(sha);
diffLabel = `Commit ${sha.slice(0, 7)}`;
selectedCommit = null;

files = [];
diffCache = new Map();
selectedFile = null;
localComments = [];
error = null;
resetDiffState();
loadDiff();
return true;
}

async function toggleCommitPicker() {
Expand All @@ -341,42 +355,42 @@
showStackPicker = !showStackPicker;
}

function selectStackBranch(branch: StackBranchInfo) {
async function selectStackBranch(branch: StackBranchInfo): Promise<boolean> {
if (!(await flushCommentEditorsForDiffChange())) return false;

stackViewTarget = branch;
diffSpec = commands.specStackBranch(branch.name, branch.parentRef);
diffLabel = `Stack: ${branch.name.split('/').pop()}`;
diffMode = 'stack';
showStackPicker = false;

files = [];
diffCache = new Map();
selectedFile = null;
localComments = [];
error = null;
resetDiffState();
loadDiff();
return true;
}

function selectStackCommittedOnly() {
if (!stackInfo) return;
async function selectStackCommittedOnly(): Promise<boolean> {
if (!stackInfo) return false;
if (!(await flushCommentEditorsForDiffChange())) return false;

stackViewTarget = null;
diffSpec = commands.specStackCommitted(stackInfo.parentBranch);
diffLabel = `Stack vs ${stackInfo.parentBranch.split('/').pop()} (committed)`;
diffMode = 'stack';
showStackPicker = false;

files = [];
diffCache = new Map();
selectedFile = null;
localComments = [];
error = null;
resetDiffState();
loadDiff();
return true;
}

// ==========================================================================
// Load diff
// ==========================================================================

async function loadDiff() {
if (!(await flushCommentEditorsForDiffChange())) return;

loading = true;
error = null;
try {
Expand All @@ -394,7 +408,10 @@
}
}

async function selectFile(path: string | null) {
async function selectFile(path: string | null): Promise<boolean> {
if (path === selectedFile) return true;
if (!(await flushCommentEditorsForDiffChange())) return false;

const thisGeneration = ++selectionGeneration;

// Handle search-related behavior (expand and select first result)
Expand All @@ -408,7 +425,7 @@
loadingFile = path;
try {
const diff = await commands.getFileDiff(diffSpec, path);
if (selectionGeneration !== thisGeneration) return;
if (selectionGeneration !== thisGeneration) return false;
const newCache = new Map(diffCache);
newCache.set(path, diff);
diffCache = newCache;
Expand All @@ -418,6 +435,8 @@
loadingFile = null;
}
}

return true;
}

// ==========================================================================
Expand All @@ -426,7 +445,11 @@

let nextCommentId = 0;

async function handleAddComment(path: string, span: Span, content: string): Promise<void> {
async function handleAddComment(
path: string,
span: Span,
content: string
): Promise<Comment | null> {
const comment: Comment = {
id: `local-${++nextCommentId}`,
path,
Expand All @@ -443,6 +466,7 @@
commitSessionId: null,
};
localComments = [...localComments, comment];
return comment;
}

async function handleUpdateComment(commentId: string, content: string): Promise<void> {
Expand Down Expand Up @@ -475,7 +499,7 @@
// ==========================================================================

function handleSelectFile(file: FileEntry) {
selectFile(file.path);
void selectFile(file.path);
}

// Load a file's diff without changing the selection (for search)
Expand Down Expand Up @@ -514,6 +538,8 @@
// ==========================================================================

async function handleFolderSelect(path: string) {
if (!(await flushCommentEditorsForDiffChange())) return;

showFolderPicker = false;
try {
await commands.setRepoPath(path);
Expand All @@ -527,11 +553,7 @@
diffMode = 'all';
diffSpec = commands.specUncommitted();
diffLabel = 'All Changes';
files = [];
diffCache = new Map();
selectedFile = null;
localComments = [];
error = null;
resetDiffState();

// Fetch repo info without triggering a diff load yet —
// we need to check for a Graphite stack first to avoid a race
Expand All @@ -543,7 +565,7 @@
const si = await commands.getStackInfo();
stackInfo = si;
if (si) {
setMode('stack');
await setMode('stack');
} else {
loadDiff();
}
Expand Down Expand Up @@ -602,7 +624,7 @@
}

// Select the file and scroll to the match
await selectFile(filePath);
if (!(await selectFile(filePath))) return;
// Scroll to the specific line
lineJumpToken += 1;
jumpToLine = { lineIndex: match.lineIndex, token: lineJumpToken };
Expand Down Expand Up @@ -936,6 +958,7 @@
</div>
{:else}
<DiffViewer
bind:this={diffViewer}
diff={currentDiff}
comments={localComments.filter((c) => c.path === selectedFile)}
{jumpToLine}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import { onMount } from 'svelte';
import { onMount, tick } from 'svelte';
import Send from '@lucide/svelte/icons/send';
import Spinner from '../../shared/Spinner.svelte';
import { Button } from '$lib/components/ui/button';
Expand All @@ -25,7 +25,8 @@
githubRepo?: string;
subpath?: string | null;
isRemote: boolean;
onStarted: () => void;
onBeforeStart?: () => boolean | Promise<boolean>;
onStarted: () => void | Promise<void>;
}

let {
Expand All @@ -38,6 +39,7 @@
githubRepo,
subpath,
isRemote,
onBeforeStart = () => true,
onStarted,
}: Props = $props();

Expand Down Expand Up @@ -141,16 +143,21 @@
}

async function handleSubmit() {
let finalPrompt = draftPrompt.trim();
if (!finalPrompt || starting) return;

// Prepend a reference to the review when launched from a review context
if (reviewId) {
finalPrompt = `Re: #review:${reviewId}\n${finalPrompt}`;
}
if (starting || !draftPrompt.trim()) return;

starting = true;
try {
if (!(await onBeforeStart())) return;
await tick();

let finalPrompt = draftPrompt.trim();
if (!finalPrompt) return;

// Prepend a reference to the review when launched from a review context
if (reviewId) {
finalPrompt = `Re: #review:${reviewId}\n${finalPrompt}`;
}

await refreshQueueState(true);

const launchContext = {
Expand All @@ -172,7 +179,7 @@
launchContext
);

onStarted();
await onStarted();
} catch (e) {
toast.error('Unable to start commit session', {
description: e instanceof Error ? e.message : String(e),
Expand Down
20 changes: 16 additions & 4 deletions apps/staged/src/lib/features/diff/DiffFileTreeSection.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
isOpen: boolean;
fileResults: Map<string, FileSearchResult>;
collapsedSearchResults: Set<string>;
currentResultIndex: number;
};
toggleSearchResults: (filePath: string) => void;
areSearchResultsCollapsed: (filePath: string) => boolean;
Expand All @@ -42,7 +43,7 @@
diffCache: Map<string, FileDiff>;
};
getCurrentDiff: () => FileDiff | null;
selectFile: (path: string) => Promise<void>;
selectFile: (path: string) => Promise<boolean | void>;
}

interface Props {
Expand All @@ -56,7 +57,7 @@
selectedFile: string | null;
isCollapsed: (path: string) => boolean;
onToggleDir: (path: string) => void;
onSelectFile: (file: FileEntry) => void;
onSelectFile: (file: FileEntry) => boolean | void | Promise<boolean | void>;
onToggleReviewed: (event: MouseEvent | KeyboardEvent, file: FileEntry) => void | Promise<void>;
onJumpToLine?: (lineIndex: number) => void;
searchState?: SearchStateHandle;
Expand Down Expand Up @@ -129,16 +130,27 @@
) {
if (!searchState || !diffViewerState) return;

const previousResultIndex = searchState.state.currentResultIndex;
const wasCollapsed = searchState.areSearchResultsCollapsed(filePath);

// Update current result index
searchState.setCurrentResult(globalIndex);

// Auto-expand search results for this file
if (searchState.areSearchResultsCollapsed(filePath)) {
if (wasCollapsed) {
searchState.toggleSearchResults(filePath);
}

// Select the file and scroll to the match
await diffViewerState.selectFile(filePath);
const selected = (await diffViewerState.selectFile(filePath)) !== false;
if (!selected) {
searchState.setCurrentResult(previousResultIndex);
if (wasCollapsed) {
searchState.toggleSearchResults(filePath);
}
return;
}

// Scroll to the specific line
if (onJumpToLine) {
onJumpToLine(match.lineIndex);
Expand Down
Loading