Diff streaming improvement (#459)

* Diffs are PatchType

* Added files don't have old content

* Improve styles

* Lints

* Update readme
This commit is contained in:
Louis Knight-Webb
2025-08-12 18:45:47 +01:00
committed by GitHub
parent 0fdc73f8b7
commit bbcf00093b
6 changed files with 64 additions and 28 deletions

View File

@@ -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).
<p align="center">
<a href="https://vibekanban.com">
<picture>
@@ -15,7 +11,7 @@
<p align="center">Get 10X more out of Claude Code, Gemini CLI, Codex, Amp and other coding agents...</p>
<p align="center">
<a href="https://www.npmjs.com/package/vibe-kanban"><img alt="npm" src="https://img.shields.io/npm/v/vibe-kanban?style=flat-square" /></a>
<a href="https://github.com/BloopAI/vibe-kanban/blob/main/.github/workflows/publish.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/bloopai/vibe-kanban/.github%2Fworkflows%2Fpublish.yml?style=flat-square&branch=dev" /></a>
<a href="https://github.com/BloopAI/vibe-kanban/blob/main/.github/workflows/publish.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/BloopAI/vibe-kanban/.github%2Fworkflows%2Fpublish.yml" /></a>
</p>
![](frontend/public/vibe-kanban-screenshot-overview.png)

View File

@@ -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
let status = delta.status();
// Only build old_file for non-added entries
let old_file = if matches!(status, Delta::Added) {
None
} else {
delta
.old_file()
.path()
.map(|path| self.create_file_details(path, &delta.old_file().id(), repo));
.map(|p| self.create_file_details(p, &delta.old_file().id(), repo))
};
let new_file = delta
// Only build new_file for non-deleted entries
let new_file = if matches!(status, Delta::Deleted) {
None
} else {
delta
.new_file()
.path()
.map(|path| self.create_file_details(path, &delta.new_file().id(), repo));
.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

View File

@@ -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 (

View File

@@ -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<PatchType, { type: 'DIFF' }> => e?.type === 'DIFF'
)
.map((e) => e.content);
}, [data]);
return { diffs, isConnected, error };
};

View File

@@ -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<string, Diff>;
entries: Record<string, PatchType>;
}
interface UseDiffStreamResult {

View File

@@ -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));