Merge task attempt 92c56515-8aa3-48e8-ad77-dfc1b0dbd0eb into main

This commit is contained in:
Louis Knight-Webb
2025-06-18 08:17:18 -04:00
4 changed files with 1184 additions and 19 deletions

View File

@@ -2974,9 +2974,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.5.167",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.167.tgz",
"integrity": "sha512-LxcRvnYO5ez2bMOFpbuuVuAI5QNeY1ncVytE/KXaL6ZNfzX1yPlAO0nSOyIHx2fVAuUprMqPs/TdVhUFZy7SIQ==",
"version": "1.5.170",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.170.tgz",
"integrity": "sha512-GP+M7aeluQo9uAyiTCxgIj/j+PrWhMlY7LFVj8prlsPljd0Fdg9AprlfUi+OCSFWy9Y5/2D/Jrj9HS8Z4rpKWA==",
"dev": true,
"license": "ISC"
},
@@ -4234,9 +4234,9 @@
"license": "MIT"
},
"node_modules/postcss": {
"version": "8.5.5",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.5.tgz",
"integrity": "sha512-d/jtm+rdNT8tpXuHY5MMtcbJFBkhXE6593XVR9UoGCH8jSFGci7jGvMGH5RYd5PBJW+00NZQt6gf7CbagJCrhg==",
"version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"funding": [
{
"type": "opencollective",

View File

@@ -2,9 +2,9 @@ import { useState, useEffect } from "react";
import { useParams, useNavigate } from "react-router-dom";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { ArrowLeft, FileText } from "lucide-react";
import { ArrowLeft, FileText, ChevronDown, ChevronUp } from "lucide-react";
import { makeRequest } from "@/lib/api";
import type { WorktreeDiff, DiffChunkType } from "shared/types";
import type { WorktreeDiff, DiffChunkType, DiffChunk } from "shared/types";
interface ApiResponse<T> {
success: boolean;
@@ -25,6 +25,7 @@ export function TaskAttemptComparePage() {
const [error, setError] = useState<string | null>(null);
const [merging, setMerging] = useState(false);
const [mergeSuccess, setMergeSuccess] = useState(false);
const [expandedSections, setExpandedSections] = useState<Set<string>>(new Set());
useEffect(() => {
if (projectId && taskId && attemptId) {
@@ -119,6 +120,141 @@ export function TaskAttemptComparePage() {
}
};
interface ProcessedLine {
content: string;
chunkType: DiffChunkType;
lineNumber: number;
}
interface ProcessedSection {
type: 'context' | 'change' | 'expanded';
lines: ProcessedLine[];
expandKey?: string;
expandedAbove?: boolean;
expandedBelow?: boolean;
}
const processFileChunks = (chunks: DiffChunk[], fileIndex: number) => {
const CONTEXT_LINES = 3;
const lines: ProcessedLine[] = [];
let currentLineNumber = 1;
// Convert chunks to lines with line numbers
chunks.forEach(chunk => {
const chunkLines = chunk.content.split('\n');
chunkLines.forEach((line, index) => {
if (index < chunkLines.length - 1 || line !== '') { // Skip empty last line from split
lines.push({
content: line,
chunkType: chunk.chunk_type,
lineNumber: currentLineNumber++
});
}
});
});
const sections: ProcessedSection[] = [];
let i = 0;
while (i < lines.length) {
const line = lines[i];
if (line.chunkType === 'Equal') {
// Look for the next change or end of file
let nextChangeIndex = i + 1;
while (nextChangeIndex < lines.length && lines[nextChangeIndex].chunkType === 'Equal') {
nextChangeIndex++;
}
const contextLength = nextChangeIndex - i;
const hasNextChange = nextChangeIndex < lines.length;
const hasPrevChange = sections.length > 0 && sections[sections.length - 1].type === 'change';
if (contextLength <= CONTEXT_LINES * 2 || (!hasPrevChange && !hasNextChange)) {
// Show all context if it's short or if there are no changes around it
sections.push({
type: 'context',
lines: lines.slice(i, nextChangeIndex)
});
} else {
// Split into context sections with expandable middle
if (hasPrevChange) {
// Add context after previous change
sections.push({
type: 'context',
lines: lines.slice(i, i + CONTEXT_LINES)
});
i += CONTEXT_LINES;
}
if (hasNextChange) {
// Add expandable section
const expandStart = hasPrevChange ? i : i + CONTEXT_LINES;
const expandEnd = nextChangeIndex - CONTEXT_LINES;
if (expandEnd > expandStart) {
const expandKey = `${fileIndex}-${expandStart}-${expandEnd}`;
const isExpanded = expandedSections.has(expandKey);
if (isExpanded) {
sections.push({
type: 'expanded',
lines: lines.slice(expandStart, expandEnd),
expandKey
});
} else {
sections.push({
type: 'context',
lines: [],
expandKey
});
}
}
// Add context before next change
sections.push({
type: 'context',
lines: lines.slice(nextChangeIndex - CONTEXT_LINES, nextChangeIndex)
});
} else if (!hasPrevChange) {
// No changes around, just show first few lines
sections.push({
type: 'context',
lines: lines.slice(i, i + CONTEXT_LINES)
});
}
}
i = nextChangeIndex;
} else {
// Found a change, collect all consecutive changes
const changeStart = i;
while (i < lines.length && lines[i].chunkType !== 'Equal') {
i++;
}
sections.push({
type: 'change',
lines: lines.slice(changeStart, i)
});
}
}
return sections;
};
const toggleExpandSection = (expandKey: string) => {
setExpandedSections(prev => {
const newSet = new Set(prev);
if (newSet.has(expandKey)) {
newSet.delete(expandKey);
} else {
newSet.add(expandKey);
}
return newSet;
});
};
if (loading) {
return (
<div className="min-h-screen bg-background flex items-center justify-center">
@@ -199,16 +335,50 @@ export function TaskAttemptComparePage() {
</p>
</div>
<div className="max-h-[600px] overflow-y-auto">
{file.chunks.map((chunk, chunkIndex) =>
chunk.content.split('\n').map((line, lineIndex) => (
<div
key={`${chunkIndex}-${lineIndex}`}
className={getChunkClassName(chunk.chunk_type)}
>
{getChunkPrefix(chunk.chunk_type)}{line}
{processFileChunks(file.chunks, fileIndex).map((section, sectionIndex) => {
if (section.type === 'context' && section.lines.length === 0 && section.expandKey) {
// Render expand button
const lineCount = parseInt(section.expandKey.split('-')[2]) - parseInt(section.expandKey.split('-')[1]);
return (
<div key={`expand-${section.expandKey}`}>
<Button
variant="ghost"
size="sm"
onClick={() => toggleExpandSection(section.expandKey!)}
className="w-full h-8 text-xs text-blue-600 hover:text-blue-800 hover:bg-blue-50 border-t border-b border-gray-200 rounded-none"
>
<ChevronDown className="h-3 w-3 mr-1" />
Show {lineCount} more lines
</Button>
</div>
);
}
// Render lines (context, change, or expanded)
return (
<div key={`section-${sectionIndex}`}>
{section.type === 'expanded' && section.expandKey && (
<Button
variant="ghost"
size="sm"
onClick={() => toggleExpandSection(section.expandKey!)}
className="w-full h-8 text-xs text-blue-600 hover:text-blue-800 hover:bg-blue-50 border-t border-b border-gray-200 rounded-none"
>
<ChevronUp className="h-3 w-3 mr-1" />
Hide expanded lines
</Button>
)}
{section.lines.map((line, lineIndex) => (
<div
key={`${sectionIndex}-${lineIndex}`}
className={getChunkClassName(line.chunkType)}
>
{getChunkPrefix(line.chunkType)}{line.content}
</div>
))}
</div>
))
)}
);
})}
</div>
</div>
))}

996
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -14,7 +14,8 @@
"generate-types": "cd backend && cargo run --bin generate_types"
},
"devDependencies": {
"concurrently": "^8.2.2"
"concurrently": "^8.2.2",
"vite": "^6.3.5"
},
"engines": {
"node": ">=18",