feat(extension): complete VS Code extension with kanban board interface (#997)

---------
Co-authored-by: DavidMaliglowka <13022280+DavidMaliglowka@users.noreply.github.com>
Co-authored-by: Ralph Khreish <35776126+Crunchyman-ralph@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
DavidMaliglowka
2025-08-01 07:04:22 -05:00
committed by GitHub
parent 60c03c548d
commit 64302dc191
101 changed files with 20608 additions and 181 deletions

View File

@@ -0,0 +1,229 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useVSCodeContext } from '../contexts/VSCodeContext';
import type { TaskMasterTask, TaskUpdates } from '../types';
// Query keys factory
export const taskKeys = {
all: ['tasks'] as const,
lists: () => [...taskKeys.all, 'list'] as const,
list: (filters: { tag?: string; status?: string }) =>
[...taskKeys.lists(), filters] as const,
details: () => [...taskKeys.all, 'detail'] as const,
detail: (id: string) => [...taskKeys.details(), id] as const
};
// Hook to fetch all tasks
export function useTasks(options?: { tag?: string; status?: string }) {
const { sendMessage } = useVSCodeContext();
return useQuery({
queryKey: taskKeys.list(options || {}),
queryFn: async () => {
console.log('🔍 Fetching tasks with options:', options);
const response = await sendMessage({
type: 'getTasks',
data: {
tag: options?.tag,
withSubtasks: true
}
});
console.log('📋 Tasks fetched:', response);
return response as TaskMasterTask[];
},
staleTime: 0 // Consider data stale immediately
});
}
// Hook to fetch a single task with full details
export function useTaskDetails(taskId: string) {
const { sendMessage } = useVSCodeContext();
return useQuery({
queryKey: taskKeys.detail(taskId),
queryFn: async () => {
const response = await sendMessage({
type: 'mcpRequest',
tool: 'get_task',
params: {
id: taskId
}
});
// Parse the MCP response
let fullTaskData = null;
if (response?.data?.content?.[0]?.text) {
try {
const parsed = JSON.parse(response.data.content[0].text);
fullTaskData = parsed.data;
} catch (e) {
console.error('Failed to parse MCP response:', e);
}
} else if (response?.data?.data) {
fullTaskData = response.data.data;
}
return fullTaskData as TaskMasterTask;
},
enabled: !!taskId
});
}
// Hook to update task status
export function useUpdateTaskStatus() {
const { sendMessage } = useVSCodeContext();
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({
taskId,
newStatus
}: {
taskId: string;
newStatus: TaskMasterTask['status'];
}) => {
const response = await sendMessage({
type: 'updateTaskStatus',
data: { taskId, newStatus }
});
return { taskId, newStatus, response };
},
// Optimistic update to prevent snap-back
onMutate: async ({ taskId, newStatus }) => {
// Cancel any outgoing refetches
await queryClient.cancelQueries({ queryKey: taskKeys.all });
// Snapshot the previous value
const previousTasks = queryClient.getQueriesData({
queryKey: taskKeys.all
});
// Optimistically update all task queries
queryClient.setQueriesData({ queryKey: taskKeys.all }, (old: any) => {
if (!old) return old;
// Handle both array and object responses
if (Array.isArray(old)) {
return old.map((task: TaskMasterTask) =>
task.id === taskId ? { ...task, status: newStatus } : task
);
}
return old;
});
// Return a context object with the snapshot
return { previousTasks };
},
// If the mutation fails, roll back to the previous value
onError: (err, variables, context) => {
if (context?.previousTasks) {
context.previousTasks.forEach(([queryKey, data]) => {
queryClient.setQueryData(queryKey, data);
});
}
},
// Always refetch after error or success to ensure consistency
onSettled: () => {
queryClient.invalidateQueries({ queryKey: taskKeys.all });
}
});
}
// Hook to update task content
export function useUpdateTask() {
const { sendMessage } = useVSCodeContext();
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({
taskId,
updates,
options = {}
}: {
taskId: string;
updates: TaskUpdates | { description: string };
options?: { append?: boolean; research?: boolean };
}) => {
console.log('🔄 Updating task:', taskId, updates, options);
const response = await sendMessage({
type: 'updateTask',
data: { taskId, updates, options }
});
console.log('📥 Update task response:', response);
// Check for error in response
if (response && typeof response === 'object' && 'error' in response) {
throw new Error(response.error || 'Failed to update task');
}
return response;
},
onSuccess: async (data, variables) => {
console.log('✅ Task update successful, invalidating all task queries');
console.log('Response data:', data);
console.log('Task ID:', variables.taskId);
// Invalidate ALL task-related queries (same as handleRefresh)
await queryClient.invalidateQueries({
queryKey: taskKeys.all
});
console.log(
'🔄 All task queries invalidated for task:',
variables.taskId
);
}
});
}
// Hook to update subtask
export function useUpdateSubtask() {
const { sendMessage } = useVSCodeContext();
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({
taskId,
prompt,
options = {}
}: {
taskId: string;
prompt: string;
options?: { research?: boolean };
}) => {
console.log('🔄 Updating subtask:', taskId, prompt, options);
const response = await sendMessage({
type: 'updateSubtask',
data: { taskId, prompt, options }
});
console.log('📥 Update subtask response:', response);
// Check for error in response
if (response && typeof response === 'object' && 'error' in response) {
throw new Error(response.error || 'Failed to update subtask');
}
return response;
},
onSuccess: async (data, variables) => {
console.log(
'✅ Subtask update successful, invalidating all task queries'
);
console.log('Subtask ID:', variables.taskId);
// Invalidate ALL task-related queries (same as handleRefresh)
await queryClient.invalidateQueries({
queryKey: taskKeys.all
});
console.log(
'🔄 All task queries invalidated for subtask:',
variables.taskId
);
}
});
}