mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-03-19 18:58:47 +08:00
fix: merge PR #139 partial — hook path fixes, TaskDrawer multi-source files, lite-tasks URL params
Cherry-pick valid changes from PR #139 (fix/webuibugs-20260314).
CodexLens build tools check dropped — target files deleted in 398601f8.
Changes included:
- Fix hook query key (rulesList → hooksList) and projectPath in cache ops
- Pass projectPath to hook template install API calls
- TaskDrawer: get files from nt.files, flowControl.target_files, or sourceRaw.file
- LiteTasksPage: support URL params (:subType/:id) to auto-open task drawer
- Add lite-tasks/:subType/:id route
Closes #139
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useWorkflowStore, selectProjectPath } from '@/stores/workflowStore';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -139,6 +140,7 @@ export function HookWizard({
|
||||
const [detectedPlatform, setDetectedPlatform] = useState<Platform>('linux');
|
||||
const [scope, setScope] = useState<'project' | 'global'>('project');
|
||||
const [saving, setSaving] = useState(false);
|
||||
const projectPath = useWorkflowStore(selectProjectPath);
|
||||
|
||||
// Fetch available skills for skill-context wizard
|
||||
const { data: skillsData, isLoading: skillsLoading } = useQuery<SkillsResponse>({
|
||||
@@ -207,6 +209,7 @@ export function HookWizard({
|
||||
body: JSON.stringify({
|
||||
templateId: 'memory-auto-compress',
|
||||
scope,
|
||||
projectPath,
|
||||
}),
|
||||
});
|
||||
const result = await response.json();
|
||||
@@ -229,6 +232,7 @@ export function HookWizard({
|
||||
body: JSON.stringify({
|
||||
templateId,
|
||||
scope,
|
||||
projectPath,
|
||||
}),
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
@@ -104,12 +104,15 @@ export function TaskDrawer({ task, isOpen, onClose }: TaskDrawerProps) {
|
||||
const dependsOn = nt.depends_on || [];
|
||||
const preAnalysis = nt.pre_analysis || flowControl?.pre_analysis || [];
|
||||
const implSteps = nt.implementation || flowControl?.implementation_approach || [];
|
||||
const taskFiles = nt.files || flowControl?.target_files || [];
|
||||
|
||||
// Get files from multiple sources: nt.files, flowControl.target_files, or sourceRaw.file
|
||||
const rawData = nt._raw as Record<string, unknown> | undefined;
|
||||
const sourceRaw = (rawData?._raw as Record<string, unknown>) || rawData;
|
||||
const rawFile = sourceRaw?.file as string | undefined;
|
||||
const taskFiles = nt.files || flowControl?.target_files || (rawFile ? [{ path: rawFile }] : []);
|
||||
const taskScope = nt.scope;
|
||||
|
||||
// Detect if task supports status tracking (new format has explicit status/status_history)
|
||||
const rawData = nt._raw as Record<string, unknown> | undefined;
|
||||
const sourceRaw = (rawData?._raw as Record<string, unknown>) || rawData;
|
||||
const hasStatusTracking = sourceRaw
|
||||
? (sourceRaw.status !== undefined || sourceRaw.status_history !== undefined)
|
||||
: false;
|
||||
@@ -391,7 +394,7 @@ export function TaskDrawer({ task, isOpen, onClose }: TaskDrawerProps) {
|
||||
<TabsContent value="files" className="mt-4 pb-6">
|
||||
{hasFiles ? (
|
||||
<div className="space-y-3">
|
||||
{flowControl?.target_files?.map((file, index) => {
|
||||
{taskFiles.map((file, index) => {
|
||||
// Support multiple file formats: string, { path: string }, { name: string }, { path, name }
|
||||
let displayPath: string;
|
||||
if (typeof file === 'string') {
|
||||
|
||||
@@ -445,7 +445,7 @@ export function useHooks(options: UseHooksOptions = {}): UseHooksReturn {
|
||||
const queryEnabled = enabled && !!projectPath;
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: workspaceQueryKeys.rulesList(projectPath),
|
||||
queryKey: workspaceQueryKeys.hooksList(projectPath),
|
||||
queryFn: () => fetchHooks(projectPath),
|
||||
staleTime,
|
||||
enabled: queryEnabled,
|
||||
@@ -478,15 +478,17 @@ export function useHooks(options: UseHooksOptions = {}): UseHooksReturn {
|
||||
|
||||
export function useToggleHook() {
|
||||
const queryClient = useQueryClient();
|
||||
const projectPath = useWorkflowStore(selectProjectPath);
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: ({ hookName, enabled }: { hookName: string; enabled: boolean }) =>
|
||||
toggleHook(hookName, enabled),
|
||||
onMutate: async ({ hookName, enabled }) => {
|
||||
await queryClient.cancelQueries({ queryKey: hooksKeys.all });
|
||||
const previousHooks = queryClient.getQueryData<HooksResponse>(hooksKeys.lists());
|
||||
const queryKey = workspaceQueryKeys.hooksList(projectPath || '');
|
||||
await queryClient.cancelQueries({ queryKey });
|
||||
const previousHooks = queryClient.getQueryData<HooksResponse>(queryKey);
|
||||
|
||||
queryClient.setQueryData<HooksResponse>(hooksKeys.lists(), (old) => {
|
||||
queryClient.setQueryData<HooksResponse>(queryKey, (old) => {
|
||||
if (!old) return old;
|
||||
return {
|
||||
hooks: old.hooks.map((h) => (h.name === hookName ? { ...h, enabled } : h)),
|
||||
@@ -496,12 +498,13 @@ export function useToggleHook() {
|
||||
return { previousHooks };
|
||||
},
|
||||
onError: (_error, _vars, context) => {
|
||||
const queryKey = workspaceQueryKeys.hooksList(projectPath || '');
|
||||
if (context?.previousHooks) {
|
||||
queryClient.setQueryData(hooksKeys.lists(), context.previousHooks);
|
||||
queryClient.setQueryData(queryKey, context.previousHooks);
|
||||
}
|
||||
},
|
||||
onSettled: () => {
|
||||
queryClient.invalidateQueries({ queryKey: hooksKeys.all });
|
||||
queryClient.invalidateQueries({ queryKey: workspaceQueryKeys.hooksList(projectPath || '') });
|
||||
},
|
||||
});
|
||||
|
||||
@@ -528,10 +531,11 @@ export function useDeleteHook() {
|
||||
});
|
||||
},
|
||||
onMutate: async (hook) => {
|
||||
await queryClient.cancelQueries({ queryKey: hooksKeys.all });
|
||||
const previousHooks = queryClient.getQueryData<HooksResponse>(hooksKeys.lists());
|
||||
const queryKey = workspaceQueryKeys.hooksList(projectPath || '');
|
||||
await queryClient.cancelQueries({ queryKey });
|
||||
const previousHooks = queryClient.getQueryData<HooksResponse>(queryKey);
|
||||
|
||||
queryClient.setQueryData<HooksResponse>(hooksKeys.lists(), (old) => {
|
||||
queryClient.setQueryData<HooksResponse>(queryKey, (old) => {
|
||||
if (!old) return old;
|
||||
return {
|
||||
hooks: old.hooks.filter((h) => h.name !== hook.name),
|
||||
@@ -541,12 +545,13 @@ export function useDeleteHook() {
|
||||
return { previousHooks };
|
||||
},
|
||||
onError: (_error, _hook, context) => {
|
||||
const queryKey = workspaceQueryKeys.hooksList(projectPath || '');
|
||||
if (context?.previousHooks) {
|
||||
queryClient.setQueryData(hooksKeys.lists(), context.previousHooks);
|
||||
queryClient.setQueryData(queryKey, context.previousHooks);
|
||||
}
|
||||
},
|
||||
onSettled: () => {
|
||||
queryClient.invalidateQueries({ queryKey: hooksKeys.all });
|
||||
queryClient.invalidateQueries({ queryKey: workspaceQueryKeys.hooksList(projectPath || '') });
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import { useState, useMemo } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useWorkflowStore, selectProjectPath } from '@/stores/workflowStore';
|
||||
import {
|
||||
GitFork,
|
||||
Plus,
|
||||
@@ -157,6 +158,7 @@ export function HookManagerPage() {
|
||||
const { hooks, enabledCount, totalCount, isLoading, refetch } = useHooks();
|
||||
const { toggleHook } = useToggleHook();
|
||||
const { deleteHook } = useDeleteHook();
|
||||
const projectPath = useWorkflowStore(selectProjectPath);
|
||||
|
||||
// Convert hooks to HookCardData and filter by search query and trigger type
|
||||
const filteredHooks = useMemo(() => {
|
||||
@@ -291,6 +293,7 @@ export function HookManagerPage() {
|
||||
body: JSON.stringify({
|
||||
templateId,
|
||||
scope: 'project',
|
||||
projectPath,
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ import { TabsNavigation } from '@/components/ui/TabsNavigation';
|
||||
import { TaskDrawer } from '@/components/shared/TaskDrawer';
|
||||
import { fetchLiteSessionContext, type LiteTask, type LiteTaskSession, type LiteSessionContext, type RoundSynthesis, type MultiCliContextPackage } from '@/lib/api';
|
||||
import { LiteContextContent } from '@/components/lite-tasks/LiteContextContent';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
type LiteTaskTab = 'lite-plan' | 'lite-fix' | 'multi-cli-plan';
|
||||
@@ -1205,11 +1205,13 @@ function ExpandedMultiCliPanel({
|
||||
*/
|
||||
export function LiteTasksPage() {
|
||||
const navigate = useNavigate();
|
||||
const { formatMessage } = useIntl();
|
||||
const { subType, id } = useParams<{ subType?: string; id?: string }>();
|
||||
const { litePlan, liteFix, multiCliPlan, isLoading, error, refetch } = useLiteTasks();
|
||||
const { formatMessage } = useIntl();
|
||||
const [activeTab, setActiveTab] = React.useState<LiteTaskTab>('lite-plan');
|
||||
const [expandedSessionId, setExpandedSessionId] = React.useState<string | null>(null);
|
||||
const [selectedTask, setSelectedTask] = React.useState<LiteTask | null>(null);
|
||||
const paramsInitialized = React.useRef(false);
|
||||
const [searchQuery, setSearchQuery] = React.useState('');
|
||||
const [sortField, setSortField] = React.useState<SortField>('date');
|
||||
const [sortOrder, setSortOrder] = React.useState<SortOrder>('desc');
|
||||
@@ -1259,6 +1261,34 @@ export function LiteTasksPage() {
|
||||
const filteredLiteFix = React.useMemo(() => filterAndSort(liteFix), [liteFix, filterAndSort]);
|
||||
const filteredMultiCliPlan = React.useMemo(() => filterAndSort(multiCliPlan), [multiCliPlan, filterAndSort]);
|
||||
|
||||
// Handle URL params to auto-open task drawer
|
||||
React.useEffect(() => {
|
||||
if (!id || paramsInitialized.current) return;
|
||||
if (isLoading) return;
|
||||
|
||||
// Find the session containing this task
|
||||
const allSessions = [...litePlan, ...liteFix, ...multiCliPlan];
|
||||
const targetSession = allSessions.find(session => session.id === id);
|
||||
|
||||
if (targetSession) {
|
||||
paramsInitialized.current = true;
|
||||
|
||||
// Set active tab based on subType
|
||||
if (subType === 'lite-plan') setActiveTab('lite-plan');
|
||||
else if (subType === 'lite-fix') setActiveTab('lite-fix');
|
||||
else if (subType === 'multi-cli-plan') setActiveTab('multi-cli-plan');
|
||||
|
||||
// Expand the session
|
||||
setExpandedSessionId(id);
|
||||
|
||||
// Find and select the task
|
||||
const task = targetSession.tasks?.find(t => t.task_id === id || t.id === id);
|
||||
if (task) {
|
||||
setSelectedTask(task);
|
||||
}
|
||||
}
|
||||
}, [id, subType, isLoading, litePlan, liteFix, multiCliPlan]);
|
||||
|
||||
// Toggle sort
|
||||
const toggleSort = (field: SortField) => {
|
||||
if (sortField === field) {
|
||||
|
||||
@@ -92,6 +92,10 @@ const routes: RouteObject[] = [
|
||||
path: 'sessions/:sessionId/review',
|
||||
element: withErrorHandling(<ReviewSessionPage />),
|
||||
},
|
||||
{
|
||||
path: 'lite-tasks/:subType/:id',
|
||||
element: withErrorHandling(<LiteTasksPage />),
|
||||
},
|
||||
{
|
||||
path: 'lite-tasks',
|
||||
element: withErrorHandling(<LiteTasksPage />),
|
||||
|
||||
Reference in New Issue
Block a user