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"> <p align="center">
<a href="https://vibekanban.com"> <a href="https://vibekanban.com">
<picture> <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">Get 10X more out of Claude Code, Gemini CLI, Codex, Amp and other coding agents...</p>
<p align="center"> <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://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> </p>
![](frontend/public/vibe-kanban-screenshot-overview.png) ![](frontend/public/vibe-kanban-screenshot-overview.png)

View File

@@ -342,26 +342,37 @@ impl GitService {
let mut file_diffs = Vec::new(); let mut file_diffs = Vec::new();
diff.foreach( diff.foreach(
&mut |delta, _progress| { &mut |delta, _| {
// Skip unreadable entries
if delta.status() == Delta::Unreadable { if delta.status() == Delta::Unreadable {
return true; return true;
} }
let old_file = delta let status = delta.status();
.old_file()
.path()
.map(|path| self.create_file_details(path, &delta.old_file().id(), repo));
let new_file = delta // Only build old_file for non-added entries
.new_file() let old_file = if matches!(status, Delta::Added) {
.path() None
.map(|path| self.create_file_details(path, &delta.new_file().id(), repo)); } 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 { file_diffs.push(Diff {
old_file, old_file,
new_file, new_file,
hunks: vec![], // Left empty as requested hunks: vec![], // still empty
}); });
true true

View File

@@ -1,5 +1,5 @@
import { generateDiffFile } from '@git-diff-view/file'; 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 { useMemo, useContext, useCallback, useState, useEffect } from 'react';
import { TaskSelectedAttemptContext } from '@/components/context/taskDetailsContext.ts'; import { TaskSelectedAttemptContext } from '@/components/context/taskDetailsContext.ts';
import { Diff } from 'shared/types'; import { Diff } from 'shared/types';
@@ -10,13 +10,13 @@ import DiffCard from '@/components/DiffCard';
function DiffTab() { function DiffTab() {
const { selectedAttempt } = useContext(TaskSelectedAttemptContext); const { selectedAttempt } = useContext(TaskSelectedAttemptContext);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const { data, error } = useDiffStream(selectedAttempt?.id ?? null, true); const { diffs, error } = useDiffEntries(selectedAttempt?.id ?? null, true);
useEffect(() => { useEffect(() => {
if (data && Object.keys(data?.entries).length > 0 && loading) { if (diffs.length > 0 && loading) {
setLoading(false); setLoading(false);
} }
}, [data]); }, [diffs, loading]);
const createDiffFile = useCallback((diff: Diff) => { const createDiffFile = useCallback((diff: Diff) => {
const oldFileName = diff.oldFile?.fileName || 'old'; const oldFileName = diff.oldFile?.fileName || 'old';
@@ -42,12 +42,10 @@ function DiffTab() {
}, []); }, []);
const diffFiles = useMemo(() => { const diffFiles = useMemo(() => {
if (!data) return []; return diffs
return Object.values(data.entries) .map((diff) => createDiffFile(diff))
.filter((e: any) => e?.type === 'DIFF')
.map((e: any) => createDiffFile(e.content as Diff))
.filter((diffFile) => diffFile !== null); .filter((diffFile) => diffFile !== null);
}, [data, createDiffFile]); }, [diffs, createDiffFile]);
if (error) { if (error) {
return ( 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 { useCallback } from 'react';
import type { Diff } from 'shared/types'; import type { PatchType } from 'shared/types';
import { useJsonPatchStream } from './useJsonPatchStream'; import { useJsonPatchStream } from './useJsonPatchStream';
interface DiffState { interface DiffState {
entries: Record<string, Diff>; entries: Record<string, PatchType>;
} }
interface UseDiffStreamResult { interface UseDiffStreamResult {

View File

@@ -366,7 +366,11 @@
--diff-border--: var(--background); --diff-border--: var(--background);
--diff-add-content--: hsl(var(--console-success) / 0.2); --diff-add-content--: hsl(var(--console-success) / 0.2);
--diff-del-content--: hsl(var(--console-error) / 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-del-lineNumber--: hsl(var(--console-error) / 0.2);
--diff-plain-content--: hsl(var(--background)); --diff-plain-content--: hsl(var(--background));
--diff-expand-content--: hsl(var(--background)); --diff-expand-content--: hsl(var(--background));