Add task attempt ID to URL (vibe-kanban) (#463)
* Commit changes from coding agent for task attempt 2a74dbe9-84df-42c8-990e-bd12ad882576 * Commit changes from coding agent for task attempt 2a74dbe9-84df-42c8-990e-bd12ad882576 * Cleanup script changes for task attempt 2a74dbe9-84df-42c8-990e-bd12ad882576 * Commit changes from coding agent for task attempt 2a74dbe9-84df-42c8-990e-bd12ad882576 * Cleanup script changes for task attempt 2a74dbe9-84df-42c8-990e-bd12ad882576
This commit is contained in:
committed by
GitHub
parent
60e80732cd
commit
faa177fe60
@@ -161,6 +161,10 @@ function AppContent() {
|
||||
path="/projects/:projectId/tasks"
|
||||
element={<ProjectTasks />}
|
||||
/>
|
||||
<Route
|
||||
path="/projects/:projectId/tasks/:taskId/attempts/:attemptId"
|
||||
element={<ProjectTasks />}
|
||||
/>
|
||||
<Route
|
||||
path="/projects/:projectId/tasks/:taskId"
|
||||
element={<ProjectTasks />}
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
useReducer,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||
import { Play } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { attemptsApi, projectsApi } from '@/lib/api';
|
||||
@@ -92,6 +92,8 @@ function TaskDetailsToolbar() {
|
||||
const [selectedProfile, setSelectedProfile] = useState<string | null>(null);
|
||||
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const { attemptId: urlAttemptId } = useParams<{ attemptId?: string }>();
|
||||
const { system, profiles } = useUserSystem();
|
||||
|
||||
// Memoize latest attempt calculation
|
||||
@@ -156,39 +158,68 @@ function TaskDetailsToolbar() {
|
||||
});
|
||||
|
||||
if (result.length > 0) {
|
||||
// Check if there's an attempt query parameter
|
||||
// Check if we have a new latest attempt (newly created)
|
||||
const currentLatest =
|
||||
taskAttempts.length > 0
|
||||
? taskAttempts.reduce((latest, current) =>
|
||||
new Date(current.created_at) > new Date(latest.created_at)
|
||||
? current
|
||||
: latest
|
||||
)
|
||||
: null;
|
||||
|
||||
const newLatest = result.reduce((latest, current) =>
|
||||
new Date(current.created_at) > new Date(latest.created_at)
|
||||
? current
|
||||
: latest
|
||||
);
|
||||
|
||||
// If we have a new attempt that wasn't there before, navigate to it immediately
|
||||
const hasNewAttempt =
|
||||
newLatest && (!currentLatest || newLatest.id !== currentLatest.id);
|
||||
|
||||
if (hasNewAttempt) {
|
||||
// Always navigate to newly created attempts
|
||||
handleAttemptSelect(newLatest);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, follow existing logic for URL-based attempt selection
|
||||
const urlParams = new URLSearchParams(location.search);
|
||||
const attemptParam = urlParams.get('attempt');
|
||||
const queryAttemptParam = urlParams.get('attempt');
|
||||
const attemptParam = urlAttemptId || queryAttemptParam;
|
||||
|
||||
let selectedAttemptToUse: TaskAttempt;
|
||||
|
||||
if (attemptParam) {
|
||||
// Try to find the specific attempt
|
||||
const specificAttempt = result.find(
|
||||
(attempt) => attempt.id === attemptParam
|
||||
);
|
||||
if (specificAttempt) {
|
||||
selectedAttemptToUse = specificAttempt;
|
||||
} else {
|
||||
// Fall back to latest if specific attempt not found
|
||||
selectedAttemptToUse = result.reduce((latest, current) =>
|
||||
new Date(current.created_at) > new Date(latest.created_at)
|
||||
? current
|
||||
: latest
|
||||
);
|
||||
selectedAttemptToUse = newLatest;
|
||||
}
|
||||
} else {
|
||||
// Use latest attempt if no specific attempt requested
|
||||
selectedAttemptToUse = result.reduce((latest, current) =>
|
||||
new Date(current.created_at) > new Date(latest.created_at)
|
||||
? current
|
||||
: latest
|
||||
);
|
||||
selectedAttemptToUse = newLatest;
|
||||
}
|
||||
|
||||
setSelectedAttempt((prev) => {
|
||||
if (JSON.stringify(prev) === JSON.stringify(selectedAttemptToUse))
|
||||
return prev;
|
||||
|
||||
// Only navigate if we're not already on the correct attempt URL
|
||||
if (
|
||||
selectedAttemptToUse &&
|
||||
task &&
|
||||
(!urlAttemptId || urlAttemptId !== selectedAttemptToUse.id)
|
||||
) {
|
||||
navigate(
|
||||
`/projects/${projectId}/tasks/${task.id}/attempts/${selectedAttemptToUse.id}`,
|
||||
{ replace: true }
|
||||
);
|
||||
}
|
||||
|
||||
return selectedAttemptToUse;
|
||||
});
|
||||
} else {
|
||||
@@ -203,7 +234,16 @@ function TaskDetailsToolbar() {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [task, location.search, setLoading, setSelectedAttempt, setAttemptData]);
|
||||
}, [
|
||||
task,
|
||||
location.search,
|
||||
urlAttemptId,
|
||||
navigate,
|
||||
projectId,
|
||||
setLoading,
|
||||
setSelectedAttempt,
|
||||
setAttemptData,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchTaskAttempts();
|
||||
@@ -214,6 +254,20 @@ function TaskDetailsToolbar() {
|
||||
dispatch({ type: 'ENTER_CREATE_MODE' });
|
||||
}, []);
|
||||
|
||||
// Handle attempt selection with URL navigation
|
||||
const handleAttemptSelect = useCallback(
|
||||
(attempt: TaskAttempt | null) => {
|
||||
setSelectedAttempt(attempt);
|
||||
if (attempt && task) {
|
||||
navigate(
|
||||
`/projects/${projectId}/tasks/${task.id}/attempts/${attempt.id}`,
|
||||
{ replace: true }
|
||||
);
|
||||
}
|
||||
},
|
||||
[navigate, projectId, task, setSelectedAttempt]
|
||||
);
|
||||
|
||||
// Stub handlers for backward compatibility with CreateAttempt
|
||||
const setCreateAttemptBranch = useCallback(
|
||||
(branch: string | null | ((prev: string | null) => string | null)) => {
|
||||
@@ -303,6 +357,7 @@ function TaskDetailsToolbar() {
|
||||
setShowCreatePRDialog={setShowCreatePRDialog}
|
||||
creatingPR={ui.creatingPR}
|
||||
handleEnterCreateAttemptMode={handleEnterCreateAttemptMode}
|
||||
handleAttemptSelect={handleAttemptSelect}
|
||||
branches={branches}
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -48,7 +48,6 @@ import {
|
||||
TaskAttemptDataContext,
|
||||
TaskAttemptStoppingContext,
|
||||
TaskDetailsContext,
|
||||
TaskSelectedAttemptContext,
|
||||
} from '@/components/context/taskDetailsContext.ts';
|
||||
import { useConfig } from '@/components/config-provider.tsx';
|
||||
import { useKeyboardShortcuts } from '@/lib/keyboard-shortcuts.ts';
|
||||
@@ -81,6 +80,7 @@ type Props = {
|
||||
taskAttempts: TaskAttempt[];
|
||||
creatingPR: boolean;
|
||||
handleEnterCreateAttemptMode: () => void;
|
||||
handleAttemptSelect: (attempt: TaskAttempt) => void;
|
||||
branches: GitBranch[];
|
||||
};
|
||||
|
||||
@@ -92,12 +92,12 @@ function CurrentAttempt({
|
||||
taskAttempts,
|
||||
creatingPR,
|
||||
handleEnterCreateAttemptMode,
|
||||
handleAttemptSelect,
|
||||
branches,
|
||||
}: Props) {
|
||||
const { task, projectId, handleOpenInEditor, projectHasDevScript } =
|
||||
useContext(TaskDetailsContext);
|
||||
const { config } = useConfig();
|
||||
const { setSelectedAttempt } = useContext(TaskSelectedAttemptContext);
|
||||
const { isStopping, setIsStopping } = useContext(TaskAttemptStoppingContext);
|
||||
const { attemptData, fetchAttemptData, isAttemptRunning } = useContext(
|
||||
TaskAttemptDataContext
|
||||
@@ -223,10 +223,10 @@ function CurrentAttempt({
|
||||
|
||||
const handleAttemptChange = useCallback(
|
||||
(attempt: TaskAttempt) => {
|
||||
setSelectedAttempt(attempt);
|
||||
handleAttemptSelect(attempt);
|
||||
fetchAttemptData(attempt.id, attempt.task_id);
|
||||
},
|
||||
[fetchAttemptData, setSelectedAttempt]
|
||||
[fetchAttemptData, handleAttemptSelect]
|
||||
);
|
||||
|
||||
const handleMergeClick = async () => {
|
||||
|
||||
@@ -249,11 +249,14 @@ export function ProjectTasks() {
|
||||
}, []);
|
||||
|
||||
const handleViewTaskDetails = useCallback(
|
||||
(task: Task) => {
|
||||
(task: Task, attemptIdToShow?: string) => {
|
||||
// setSelectedTask(task);
|
||||
// setIsPanelOpen(true);
|
||||
// Update URL to include task ID
|
||||
navigate(`/projects/${projectId}/tasks/${task.id}`, { replace: true });
|
||||
// Update URL to include task ID and optionally attempt ID
|
||||
const targetUrl = attemptIdToShow
|
||||
? `/projects/${projectId}/tasks/${task.id}/attempts/${attemptIdToShow}`
|
||||
: `/projects/${projectId}/tasks/${task.id}`;
|
||||
navigate(targetUrl, { replace: true });
|
||||
},
|
||||
[projectId, navigate]
|
||||
);
|
||||
@@ -311,7 +314,7 @@ export function ProjectTasks() {
|
||||
// Setup keyboard shortcuts
|
||||
useKeyboardShortcuts({
|
||||
navigate,
|
||||
currentPath: `/projects/${projectId}/tasks`,
|
||||
currentPath: window.location.pathname,
|
||||
hasOpenDialog:
|
||||
isTaskDialogOpen || isTemplateManagerOpen || isProjectSettingsOpen,
|
||||
closeDialog: () => setIsTaskDialogOpen(false),
|
||||
|
||||
Reference in New Issue
Block a user