mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-03-20 19:03:51 +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 { useState, useEffect } from 'react';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
|
import { useWorkflowStore, selectProjectPath } from '@/stores/workflowStore';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -139,6 +140,7 @@ export function HookWizard({
|
|||||||
const [detectedPlatform, setDetectedPlatform] = useState<Platform>('linux');
|
const [detectedPlatform, setDetectedPlatform] = useState<Platform>('linux');
|
||||||
const [scope, setScope] = useState<'project' | 'global'>('project');
|
const [scope, setScope] = useState<'project' | 'global'>('project');
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
|
const projectPath = useWorkflowStore(selectProjectPath);
|
||||||
|
|
||||||
// Fetch available skills for skill-context wizard
|
// Fetch available skills for skill-context wizard
|
||||||
const { data: skillsData, isLoading: skillsLoading } = useQuery<SkillsResponse>({
|
const { data: skillsData, isLoading: skillsLoading } = useQuery<SkillsResponse>({
|
||||||
@@ -207,6 +209,7 @@ export function HookWizard({
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
templateId: 'memory-auto-compress',
|
templateId: 'memory-auto-compress',
|
||||||
scope,
|
scope,
|
||||||
|
projectPath,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
@@ -229,6 +232,7 @@ export function HookWizard({
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
templateId,
|
templateId,
|
||||||
scope,
|
scope,
|
||||||
|
projectPath,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|||||||
@@ -104,12 +104,15 @@ export function TaskDrawer({ task, isOpen, onClose }: TaskDrawerProps) {
|
|||||||
const dependsOn = nt.depends_on || [];
|
const dependsOn = nt.depends_on || [];
|
||||||
const preAnalysis = nt.pre_analysis || flowControl?.pre_analysis || [];
|
const preAnalysis = nt.pre_analysis || flowControl?.pre_analysis || [];
|
||||||
const implSteps = nt.implementation || flowControl?.implementation_approach || [];
|
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;
|
const taskScope = nt.scope;
|
||||||
|
|
||||||
// Detect if task supports status tracking (new format has explicit status/status_history)
|
// 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
|
const hasStatusTracking = sourceRaw
|
||||||
? (sourceRaw.status !== undefined || sourceRaw.status_history !== undefined)
|
? (sourceRaw.status !== undefined || sourceRaw.status_history !== undefined)
|
||||||
: false;
|
: false;
|
||||||
@@ -391,7 +394,7 @@ export function TaskDrawer({ task, isOpen, onClose }: TaskDrawerProps) {
|
|||||||
<TabsContent value="files" className="mt-4 pb-6">
|
<TabsContent value="files" className="mt-4 pb-6">
|
||||||
{hasFiles ? (
|
{hasFiles ? (
|
||||||
<div className="space-y-3">
|
<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 }
|
// Support multiple file formats: string, { path: string }, { name: string }, { path, name }
|
||||||
let displayPath: string;
|
let displayPath: string;
|
||||||
if (typeof file === 'string') {
|
if (typeof file === 'string') {
|
||||||
|
|||||||
@@ -445,7 +445,7 @@ export function useHooks(options: UseHooksOptions = {}): UseHooksReturn {
|
|||||||
const queryEnabled = enabled && !!projectPath;
|
const queryEnabled = enabled && !!projectPath;
|
||||||
|
|
||||||
const query = useQuery({
|
const query = useQuery({
|
||||||
queryKey: workspaceQueryKeys.rulesList(projectPath),
|
queryKey: workspaceQueryKeys.hooksList(projectPath),
|
||||||
queryFn: () => fetchHooks(projectPath),
|
queryFn: () => fetchHooks(projectPath),
|
||||||
staleTime,
|
staleTime,
|
||||||
enabled: queryEnabled,
|
enabled: queryEnabled,
|
||||||
@@ -478,15 +478,17 @@ export function useHooks(options: UseHooksOptions = {}): UseHooksReturn {
|
|||||||
|
|
||||||
export function useToggleHook() {
|
export function useToggleHook() {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
const projectPath = useWorkflowStore(selectProjectPath);
|
||||||
|
|
||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
mutationFn: ({ hookName, enabled }: { hookName: string; enabled: boolean }) =>
|
mutationFn: ({ hookName, enabled }: { hookName: string; enabled: boolean }) =>
|
||||||
toggleHook(hookName, enabled),
|
toggleHook(hookName, enabled),
|
||||||
onMutate: async ({ hookName, enabled }) => {
|
onMutate: async ({ hookName, enabled }) => {
|
||||||
await queryClient.cancelQueries({ queryKey: hooksKeys.all });
|
const queryKey = workspaceQueryKeys.hooksList(projectPath || '');
|
||||||
const previousHooks = queryClient.getQueryData<HooksResponse>(hooksKeys.lists());
|
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;
|
if (!old) return old;
|
||||||
return {
|
return {
|
||||||
hooks: old.hooks.map((h) => (h.name === hookName ? { ...h, enabled } : h)),
|
hooks: old.hooks.map((h) => (h.name === hookName ? { ...h, enabled } : h)),
|
||||||
@@ -496,12 +498,13 @@ export function useToggleHook() {
|
|||||||
return { previousHooks };
|
return { previousHooks };
|
||||||
},
|
},
|
||||||
onError: (_error, _vars, context) => {
|
onError: (_error, _vars, context) => {
|
||||||
|
const queryKey = workspaceQueryKeys.hooksList(projectPath || '');
|
||||||
if (context?.previousHooks) {
|
if (context?.previousHooks) {
|
||||||
queryClient.setQueryData(hooksKeys.lists(), context.previousHooks);
|
queryClient.setQueryData(queryKey, context.previousHooks);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onSettled: () => {
|
onSettled: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: hooksKeys.all });
|
queryClient.invalidateQueries({ queryKey: workspaceQueryKeys.hooksList(projectPath || '') });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -528,10 +531,11 @@ export function useDeleteHook() {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
onMutate: async (hook) => {
|
onMutate: async (hook) => {
|
||||||
await queryClient.cancelQueries({ queryKey: hooksKeys.all });
|
const queryKey = workspaceQueryKeys.hooksList(projectPath || '');
|
||||||
const previousHooks = queryClient.getQueryData<HooksResponse>(hooksKeys.lists());
|
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;
|
if (!old) return old;
|
||||||
return {
|
return {
|
||||||
hooks: old.hooks.filter((h) => h.name !== hook.name),
|
hooks: old.hooks.filter((h) => h.name !== hook.name),
|
||||||
@@ -541,12 +545,13 @@ export function useDeleteHook() {
|
|||||||
return { previousHooks };
|
return { previousHooks };
|
||||||
},
|
},
|
||||||
onError: (_error, _hook, context) => {
|
onError: (_error, _hook, context) => {
|
||||||
|
const queryKey = workspaceQueryKeys.hooksList(projectPath || '');
|
||||||
if (context?.previousHooks) {
|
if (context?.previousHooks) {
|
||||||
queryClient.setQueryData(hooksKeys.lists(), context.previousHooks);
|
queryClient.setQueryData(queryKey, context.previousHooks);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onSettled: () => {
|
onSettled: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: hooksKeys.all });
|
queryClient.invalidateQueries({ queryKey: workspaceQueryKeys.hooksList(projectPath || '') });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
import { useState, useMemo } from 'react';
|
import { useState, useMemo } from 'react';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
|
import { useWorkflowStore, selectProjectPath } from '@/stores/workflowStore';
|
||||||
import {
|
import {
|
||||||
GitFork,
|
GitFork,
|
||||||
Plus,
|
Plus,
|
||||||
@@ -157,6 +158,7 @@ export function HookManagerPage() {
|
|||||||
const { hooks, enabledCount, totalCount, isLoading, refetch } = useHooks();
|
const { hooks, enabledCount, totalCount, isLoading, refetch } = useHooks();
|
||||||
const { toggleHook } = useToggleHook();
|
const { toggleHook } = useToggleHook();
|
||||||
const { deleteHook } = useDeleteHook();
|
const { deleteHook } = useDeleteHook();
|
||||||
|
const projectPath = useWorkflowStore(selectProjectPath);
|
||||||
|
|
||||||
// Convert hooks to HookCardData and filter by search query and trigger type
|
// Convert hooks to HookCardData and filter by search query and trigger type
|
||||||
const filteredHooks = useMemo(() => {
|
const filteredHooks = useMemo(() => {
|
||||||
@@ -291,6 +293,7 @@ export function HookManagerPage() {
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
templateId,
|
templateId,
|
||||||
scope: 'project',
|
scope: 'project',
|
||||||
|
projectPath,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ import { TabsNavigation } from '@/components/ui/TabsNavigation';
|
|||||||
import { TaskDrawer } from '@/components/shared/TaskDrawer';
|
import { TaskDrawer } from '@/components/shared/TaskDrawer';
|
||||||
import { fetchLiteSessionContext, type LiteTask, type LiteTaskSession, type LiteSessionContext, type RoundSynthesis, type MultiCliContextPackage } from '@/lib/api';
|
import { fetchLiteSessionContext, type LiteTask, type LiteTaskSession, type LiteSessionContext, type RoundSynthesis, type MultiCliContextPackage } from '@/lib/api';
|
||||||
import { LiteContextContent } from '@/components/lite-tasks/LiteContextContent';
|
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';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
type LiteTaskTab = 'lite-plan' | 'lite-fix' | 'multi-cli-plan';
|
type LiteTaskTab = 'lite-plan' | 'lite-fix' | 'multi-cli-plan';
|
||||||
@@ -1205,11 +1205,13 @@ function ExpandedMultiCliPanel({
|
|||||||
*/
|
*/
|
||||||
export function LiteTasksPage() {
|
export function LiteTasksPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { formatMessage } = useIntl();
|
const { subType, id } = useParams<{ subType?: string; id?: string }>();
|
||||||
const { litePlan, liteFix, multiCliPlan, isLoading, error, refetch } = useLiteTasks();
|
const { litePlan, liteFix, multiCliPlan, isLoading, error, refetch } = useLiteTasks();
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
const [activeTab, setActiveTab] = React.useState<LiteTaskTab>('lite-plan');
|
const [activeTab, setActiveTab] = React.useState<LiteTaskTab>('lite-plan');
|
||||||
const [expandedSessionId, setExpandedSessionId] = React.useState<string | null>(null);
|
const [expandedSessionId, setExpandedSessionId] = React.useState<string | null>(null);
|
||||||
const [selectedTask, setSelectedTask] = React.useState<LiteTask | null>(null);
|
const [selectedTask, setSelectedTask] = React.useState<LiteTask | null>(null);
|
||||||
|
const paramsInitialized = React.useRef(false);
|
||||||
const [searchQuery, setSearchQuery] = React.useState('');
|
const [searchQuery, setSearchQuery] = React.useState('');
|
||||||
const [sortField, setSortField] = React.useState<SortField>('date');
|
const [sortField, setSortField] = React.useState<SortField>('date');
|
||||||
const [sortOrder, setSortOrder] = React.useState<SortOrder>('desc');
|
const [sortOrder, setSortOrder] = React.useState<SortOrder>('desc');
|
||||||
@@ -1259,6 +1261,34 @@ export function LiteTasksPage() {
|
|||||||
const filteredLiteFix = React.useMemo(() => filterAndSort(liteFix), [liteFix, filterAndSort]);
|
const filteredLiteFix = React.useMemo(() => filterAndSort(liteFix), [liteFix, filterAndSort]);
|
||||||
const filteredMultiCliPlan = React.useMemo(() => filterAndSort(multiCliPlan), [multiCliPlan, 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
|
// Toggle sort
|
||||||
const toggleSort = (field: SortField) => {
|
const toggleSort = (field: SortField) => {
|
||||||
if (sortField === field) {
|
if (sortField === field) {
|
||||||
|
|||||||
@@ -92,6 +92,10 @@ const routes: RouteObject[] = [
|
|||||||
path: 'sessions/:sessionId/review',
|
path: 'sessions/:sessionId/review',
|
||||||
element: withErrorHandling(<ReviewSessionPage />),
|
element: withErrorHandling(<ReviewSessionPage />),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'lite-tasks/:subType/:id',
|
||||||
|
element: withErrorHandling(<LiteTasksPage />),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'lite-tasks',
|
path: 'lite-tasks',
|
||||||
element: withErrorHandling(<LiteTasksPage />),
|
element: withErrorHandling(<LiteTasksPage />),
|
||||||
|
|||||||
Reference in New Issue
Block a user