From bbcf00093b511652f78d3552b979d08a93c13224 Mon Sep 17 00:00:00 2001 From: Louis Knight-Webb Date: Tue, 12 Aug 2025 18:45:47 +0100 Subject: [PATCH] Diff streaming improvement (#459) * Diffs are PatchType * Added files don't have old content * Improve styles * Lints * Update readme --- README.md | 6 +--- crates/services/src/services/git.rs | 33 ++++++++++++------- .../components/tasks/TaskDetails/DiffTab.tsx | 16 ++++----- frontend/src/hooks/useDiffEntries.ts | 27 +++++++++++++++ frontend/src/hooks/useDiffStream.ts | 4 +-- frontend/src/styles/diff-style-overrides.css | 6 +++- 6 files changed, 64 insertions(+), 28 deletions(-) create mode 100644 frontend/src/hooks/useDiffEntries.ts diff --git a/README.md b/README.md index 14ae5c27..4f847a41 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,3 @@ -> [!IMPORTANT] -> We're re-writing the codebase, expect a delayed response to feedback until that's shipped. The current version on NPM is stable and we expect to be releasing alpha builds of V2 within days. -> You can track progress [here](https://github.com/BloopAI/vibe-kanban/tree/deployments). -

@@ -15,7 +11,7 @@

Get 10X more out of Claude Code, Gemini CLI, Codex, Amp and other coding agents...

npm - Build status + Build status

![](frontend/public/vibe-kanban-screenshot-overview.png) diff --git a/crates/services/src/services/git.rs b/crates/services/src/services/git.rs index e9b197c4..29557eca 100644 --- a/crates/services/src/services/git.rs +++ b/crates/services/src/services/git.rs @@ -342,26 +342,37 @@ impl GitService { let mut file_diffs = Vec::new(); diff.foreach( - &mut |delta, _progress| { - // Skip unreadable entries + &mut |delta, _| { if delta.status() == Delta::Unreadable { return true; } - let old_file = delta - .old_file() - .path() - .map(|path| self.create_file_details(path, &delta.old_file().id(), repo)); + let status = delta.status(); - let new_file = delta - .new_file() - .path() - .map(|path| self.create_file_details(path, &delta.new_file().id(), repo)); + // Only build old_file for non-added entries + let old_file = if matches!(status, Delta::Added) { + None + } else { + delta + .old_file() + .path() + .map(|p| self.create_file_details(p, &delta.old_file().id(), repo)) + }; + + // Only build new_file for non-deleted entries + let new_file = if matches!(status, Delta::Deleted) { + None + } else { + delta + .new_file() + .path() + .map(|p| self.create_file_details(p, &delta.new_file().id(), repo)) + }; file_diffs.push(Diff { old_file, new_file, - hunks: vec![], // Left empty as requested + hunks: vec![], // still empty }); true diff --git a/frontend/src/components/tasks/TaskDetails/DiffTab.tsx b/frontend/src/components/tasks/TaskDetails/DiffTab.tsx index d06bca68..7c6c0dc3 100644 --- a/frontend/src/components/tasks/TaskDetails/DiffTab.tsx +++ b/frontend/src/components/tasks/TaskDetails/DiffTab.tsx @@ -1,5 +1,5 @@ import { generateDiffFile } from '@git-diff-view/file'; -import { useDiffStream } from '@/hooks/useDiffStream'; +import { useDiffEntries } from '@/hooks/useDiffEntries'; import { useMemo, useContext, useCallback, useState, useEffect } from 'react'; import { TaskSelectedAttemptContext } from '@/components/context/taskDetailsContext.ts'; import { Diff } from 'shared/types'; @@ -10,13 +10,13 @@ import DiffCard from '@/components/DiffCard'; function DiffTab() { const { selectedAttempt } = useContext(TaskSelectedAttemptContext); const [loading, setLoading] = useState(true); - const { data, error } = useDiffStream(selectedAttempt?.id ?? null, true); + const { diffs, error } = useDiffEntries(selectedAttempt?.id ?? null, true); useEffect(() => { - if (data && Object.keys(data?.entries).length > 0 && loading) { + if (diffs.length > 0 && loading) { setLoading(false); } - }, [data]); + }, [diffs, loading]); const createDiffFile = useCallback((diff: Diff) => { const oldFileName = diff.oldFile?.fileName || 'old'; @@ -42,12 +42,10 @@ function DiffTab() { }, []); const diffFiles = useMemo(() => { - if (!data) return []; - return Object.values(data.entries) - .filter((e: any) => e?.type === 'DIFF') - .map((e: any) => createDiffFile(e.content as Diff)) + return diffs + .map((diff) => createDiffFile(diff)) .filter((diffFile) => diffFile !== null); - }, [data, createDiffFile]); + }, [diffs, createDiffFile]); if (error) { return ( diff --git a/frontend/src/hooks/useDiffEntries.ts b/frontend/src/hooks/useDiffEntries.ts new file mode 100644 index 00000000..42db1def --- /dev/null +++ b/frontend/src/hooks/useDiffEntries.ts @@ -0,0 +1,27 @@ +import { useMemo } from 'react'; +import { useDiffStream } from './useDiffStream'; +import type { Diff, PatchType } from 'shared/types'; + +interface UseDiffEntriesResult { + diffs: Diff[]; + isConnected: boolean; + error: string | null; +} + +export const useDiffEntries = ( + attemptId: string | null, + enabled: boolean +): UseDiffEntriesResult => { + const { data, isConnected, error } = useDiffStream(attemptId, enabled); + + const diffs = useMemo(() => { + if (!data) return []; + return Object.values(data.entries) + .filter( + (e): e is Extract => e?.type === 'DIFF' + ) + .map((e) => e.content); + }, [data]); + + return { diffs, isConnected, error }; +}; diff --git a/frontend/src/hooks/useDiffStream.ts b/frontend/src/hooks/useDiffStream.ts index eb142d0f..ae9dba0b 100644 --- a/frontend/src/hooks/useDiffStream.ts +++ b/frontend/src/hooks/useDiffStream.ts @@ -1,9 +1,9 @@ import { useCallback } from 'react'; -import type { Diff } from 'shared/types'; +import type { PatchType } from 'shared/types'; import { useJsonPatchStream } from './useJsonPatchStream'; interface DiffState { - entries: Record; + entries: Record; } interface UseDiffStreamResult { diff --git a/frontend/src/styles/diff-style-overrides.css b/frontend/src/styles/diff-style-overrides.css index 38a6b459..ffec26de 100644 --- a/frontend/src/styles/diff-style-overrides.css +++ b/frontend/src/styles/diff-style-overrides.css @@ -366,7 +366,11 @@ --diff-border--: var(--background); --diff-add-content--: hsl(var(--console-success) / 0.2); --diff-del-content--: hsl(var(--console-error) / 0.2); - --diff-add-lineNumber--: hsl(var(--console-success) / 0.2); + --diff-add-lineNumber--: color-mix( + in srgb, + hsl(var(--console-success)) 20%, + hsl(var(--background)) 80% + ); --diff-del-lineNumber--: hsl(var(--console-error) / 0.2); --diff-plain-content--: hsl(var(--background)); --diff-expand-content--: hsl(var(--background));