feat(ui): add React Query mutation hooks

- Add feature mutations (create, update, delete with optimistic updates)
- Add auto-mode mutations (start, stop, approve plan)
- Add worktree mutations (create, delete, checkout, switch branch)
- Add settings mutations (update global/project, validate API keys)
- Add GitHub mutations (create PR, validate PR)
- Add cursor permissions mutations (apply profile, copy config)
- Add spec mutations (generate, update, save)
- Add pipeline mutations (toggle, update config)
- Add session mutations with cache invalidation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Shirone
2026-01-15 16:20:38 +01:00
parent 2bc931a8b0
commit 845674128e
9 changed files with 1859 additions and 0 deletions

View File

@@ -0,0 +1,179 @@
/**
* Spec Mutation Hooks
*
* React Query mutations for spec operations like creating, regenerating, and saving.
*/
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { getElectronAPI } from '@/lib/electron';
import { queryKeys } from '@/lib/query-keys';
import { toast } from 'sonner';
import type { FeatureCount } from '@/components/views/spec-view/types';
/**
* Input for creating a spec
*/
interface CreateSpecInput {
projectOverview: string;
generateFeatures: boolean;
analyzeProject: boolean;
featureCount?: FeatureCount;
}
/**
* Input for regenerating a spec
*/
interface RegenerateSpecInput {
projectDefinition: string;
generateFeatures: boolean;
analyzeProject: boolean;
featureCount?: FeatureCount;
}
/**
* Create a new spec for a project
*
* This mutation triggers an async spec creation process. Progress and completion
* are delivered via WebSocket events (spec_regeneration_progress, spec_regeneration_complete).
*
* @param projectPath - Path to the project
* @returns Mutation for creating specs
*
* @example
* ```tsx
* const createMutation = useCreateSpec(projectPath);
*
* createMutation.mutate({
* projectOverview: 'A todo app with...',
* generateFeatures: true,
* analyzeProject: true,
* featureCount: 50,
* });
* ```
*/
export function useCreateSpec(projectPath: string) {
return useMutation({
mutationFn: async (input: CreateSpecInput) => {
const { projectOverview, generateFeatures, analyzeProject, featureCount } = input;
const api = getElectronAPI();
if (!api.specRegeneration) {
throw new Error('Spec regeneration API not available');
}
const result = await api.specRegeneration.create(
projectPath,
projectOverview.trim(),
generateFeatures,
analyzeProject,
generateFeatures ? featureCount : undefined
);
if (!result.success) {
throw new Error(result.error || 'Failed to start spec creation');
}
return result;
},
// Toast/state updates are handled by the component since it tracks WebSocket events
});
}
/**
* Regenerate an existing spec
*
* @param projectPath - Path to the project
* @returns Mutation for regenerating specs
*/
export function useRegenerateSpec(projectPath: string) {
return useMutation({
mutationFn: async (input: RegenerateSpecInput) => {
const { projectDefinition, generateFeatures, analyzeProject, featureCount } = input;
const api = getElectronAPI();
if (!api.specRegeneration) {
throw new Error('Spec regeneration API not available');
}
const result = await api.specRegeneration.generate(
projectPath,
projectDefinition.trim(),
generateFeatures,
analyzeProject,
generateFeatures ? featureCount : undefined
);
if (!result.success) {
throw new Error(result.error || 'Failed to start spec regeneration');
}
return result;
},
});
}
/**
* Generate features from existing spec
*
* @param projectPath - Path to the project
* @returns Mutation for generating features
*/
export function useGenerateFeatures(projectPath: string) {
return useMutation({
mutationFn: async () => {
const api = getElectronAPI();
if (!api.specRegeneration) {
throw new Error('Spec regeneration API not available');
}
const result = await api.specRegeneration.generateFeatures(projectPath);
if (!result.success) {
throw new Error(result.error || 'Failed to start feature generation');
}
return result;
},
});
}
/**
* Save spec file content
*
* @param projectPath - Path to the project
* @returns Mutation for saving spec
*
* @example
* ```tsx
* const saveMutation = useSaveSpec(projectPath);
*
* saveMutation.mutate(specContent, {
* onSuccess: () => setHasChanges(false),
* });
* ```
*/
export function useSaveSpec(projectPath: string) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (content: string) => {
const api = getElectronAPI();
await api.writeFile(`${projectPath}/.automaker/app_spec.txt`, content);
return { content };
},
onSuccess: () => {
// Invalidate spec file cache
queryClient.invalidateQueries({
queryKey: queryKeys.spec.file(projectPath),
});
toast.success('Spec saved');
},
onError: (error) => {
toast.error('Failed to save spec', {
description: error instanceof Error ? error.message : 'Unknown error',
});
},
});
}