diff --git a/ccw/frontend/src/components/deepwiki/DocumentViewer.tsx b/ccw/frontend/src/components/deepwiki/DocumentViewer.tsx
index 2a2696e7..69a2db9b 100644
--- a/ccw/frontend/src/components/deepwiki/DocumentViewer.tsx
+++ b/ccw/frontend/src/components/deepwiki/DocumentViewer.tsx
@@ -3,12 +3,11 @@
// ========================================
// Displays DeepWiki documentation content with table of contents
-import { useMemo, useState } from 'react';
+import { useMemo, useState, useCallback } from 'react';
import { useIntl } from 'react-intl';
import { FileText, Hash, Clock, Sparkles, AlertCircle, Link2, Check } from 'lucide-react';
import { Card } from '@/components/ui/Card';
import { Badge } from '@/components/ui/Badge';
-import { Button } from '@/components/ui/Button';
import { cn } from '@/lib/utils';
import type { DeepWikiSymbol, DeepWikiDoc } from '@/hooks/useDeepWiki';
@@ -276,20 +275,41 @@ export function DocumentViewer({
diff --git a/ccw/frontend/src/components/hook/HookCard.tsx b/ccw/frontend/src/components/hook/HookCard.tsx
index b10d69d9..27aa6666 100644
--- a/ccw/frontend/src/components/hook/HookCard.tsx
+++ b/ccw/frontend/src/components/hook/HookCard.tsx
@@ -30,6 +30,8 @@ export interface HookCardData {
matcher?: string;
command?: string;
script?: string;
+ scope?: 'global' | 'project';
+ index?: number;
}
export interface HookCardProps {
diff --git a/ccw/frontend/src/components/layout/Sidebar.tsx b/ccw/frontend/src/components/layout/Sidebar.tsx
index ba37f41d..d0c1f61f 100644
--- a/ccw/frontend/src/components/layout/Sidebar.tsx
+++ b/ccw/frontend/src/components/layout/Sidebar.tsx
@@ -28,6 +28,7 @@ import {
FileSearch,
ScrollText,
Clock,
+ BookOpen,
} from 'lucide-react';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/Button';
@@ -92,6 +93,7 @@ const navGroupDefinitions: NavGroupDef[] = [
icon: Brain,
items: [
{ path: '/memory', labelKey: 'navigation.main.memory', icon: Brain },
+ { path: '/deepwiki', labelKey: 'navigation.main.deepwiki', icon: BookOpen },
{ path: '/skills', labelKey: 'navigation.main.skills', icon: Sparkles },
{ path: '/commands', labelKey: 'navigation.main.commands', icon: Terminal },
{ path: '/settings/rules', labelKey: 'navigation.main.rules', icon: Shield },
diff --git a/ccw/frontend/src/hooks/useCli.ts b/ccw/frontend/src/hooks/useCli.ts
index 534f1c96..062dc776 100644
--- a/ccw/frontend/src/hooks/useCli.ts
+++ b/ccw/frontend/src/hooks/useCli.ts
@@ -514,23 +514,33 @@ export function useToggleHook() {
export function useDeleteHook() {
const queryClient = useQueryClient();
+ const projectPath = useWorkflowStore(selectProjectPath);
const mutation = useMutation({
- mutationFn: (hookName: string) => deleteHook(hookName),
- onMutate: async (hookName) => {
+ mutationFn: (hook: { name: string; scope?: 'global' | 'project'; trigger: string; index?: number }) => {
+ const scope = hook.scope || 'project';
+ const hookIndex = hook.index ?? 0;
+ return deleteHook({
+ projectPath: projectPath || undefined,
+ scope,
+ event: hook.trigger,
+ hookIndex,
+ });
+ },
+ onMutate: async (hook) => {
await queryClient.cancelQueries({ queryKey: hooksKeys.all });
const previousHooks = queryClient.getQueryData(hooksKeys.lists());
queryClient.setQueryData(hooksKeys.lists(), (old) => {
if (!old) return old;
return {
- hooks: old.hooks.filter((h) => h.name !== hookName),
+ hooks: old.hooks.filter((h) => h.name !== hook.name),
};
});
return { previousHooks };
},
- onError: (_error, _hookName, context) => {
+ onError: (_error, _hook, context) => {
if (context?.previousHooks) {
queryClient.setQueryData(hooksKeys.lists(), context.previousHooks);
}
diff --git a/ccw/frontend/src/lib/api.ts b/ccw/frontend/src/lib/api.ts
index 3407492d..24cc6045 100644
--- a/ccw/frontend/src/lib/api.ts
+++ b/ccw/frontend/src/lib/api.ts
@@ -4407,9 +4407,15 @@ export async function updateHookConfig(
/**
* Delete a hook
*/
-export async function deleteHook(hookName: string): Promise {
- return fetchApi(`/api/hooks/delete/${encodeURIComponent(hookName)}`, {
+export async function deleteHook(params: {
+ projectPath?: string;
+ scope: 'global' | 'project';
+ event: string;
+ hookIndex: number;
+}): Promise<{ success: boolean }> {
+ return fetchApi<{ success: boolean }>('/api/hooks', {
method: 'DELETE',
+ body: JSON.stringify(params),
});
}
diff --git a/ccw/frontend/src/locales/en/navigation.json b/ccw/frontend/src/locales/en/navigation.json
index ea8accad..eaa14fb9 100644
--- a/ccw/frontend/src/locales/en/navigation.json
+++ b/ccw/frontend/src/locales/en/navigation.json
@@ -23,6 +23,7 @@
"skills": "Skills",
"commands": "Commands",
"memory": "Memory",
+ "deepwiki": "DeepWiki",
"prompts": "Prompt History",
"settings": "Settings",
"mcp": "MCP Servers",
diff --git a/ccw/frontend/src/locales/zh/navigation.json b/ccw/frontend/src/locales/zh/navigation.json
index e09fb9da..f0692750 100644
--- a/ccw/frontend/src/locales/zh/navigation.json
+++ b/ccw/frontend/src/locales/zh/navigation.json
@@ -23,6 +23,7 @@
"skills": "技能",
"commands": "命令",
"memory": "记忆",
+ "deepwiki": "DeepWiki",
"prompts": "提示历史",
"settings": "设置",
"mcp": "MCP 服务器",
diff --git a/ccw/frontend/src/pages/DeepWikiPage.tsx b/ccw/frontend/src/pages/DeepWikiPage.tsx
index abd2f8d6..4e3056e5 100644
--- a/ccw/frontend/src/pages/DeepWikiPage.tsx
+++ b/ccw/frontend/src/pages/DeepWikiPage.tsx
@@ -177,6 +177,7 @@ export function DeepWikiPage() {
symbols={symbols}
isLoading={docLoading}
error={docError}
+ filePath={selectedFile ?? undefined}
/>
)}
diff --git a/ccw/frontend/src/pages/HookManagerPage.tsx b/ccw/frontend/src/pages/HookManagerPage.tsx
index aaa603e3..782b4482 100644
--- a/ccw/frontend/src/pages/HookManagerPage.tsx
+++ b/ccw/frontend/src/pages/HookManagerPage.tsx
@@ -53,7 +53,7 @@ function isHookTriggerType(value: string): value is HookTriggerType {
return ['SessionStart', 'UserPromptSubmit', 'PreToolUse', 'PostToolUse', 'Stop', 'Notification', 'SubagentStart', 'SubagentStop', 'PreCompact', 'SessionEnd', 'PostToolUseFailure', 'PermissionRequest'].includes(value);
}
-function toHookCardData(hook: { name: string; description?: string; enabled: boolean; trigger: string; matcher?: string; command?: string; script?: string }): HookCardData | null {
+function toHookCardData(hook: { name: string; description?: string; enabled: boolean; trigger: string; matcher?: string; command?: string; script?: string; scope?: 'global' | 'project'; index?: number }): HookCardData | null {
if (!isHookTriggerType(hook.trigger)) {
return null;
}
@@ -64,6 +64,8 @@ function toHookCardData(hook: { name: string; description?: string; enabled: boo
trigger: hook.trigger,
matcher: hook.matcher,
command: hook.command || hook.script,
+ scope: hook.scope,
+ index: hook.index,
};
}
@@ -200,8 +202,19 @@ export function HookManagerPage() {
};
const handleDeleteClick = async (hookName: string) => {
+ // Find the hook in filteredHooks to get scope and index
+ const hook = filteredHooks.find(h => h.name === hookName);
+ if (!hook) {
+ console.error('Hook not found:', hookName);
+ return;
+ }
try {
- await deleteHook(hookName);
+ await deleteHook({
+ name: hook.name,
+ scope: hook.scope,
+ trigger: hook.trigger,
+ index: hook.index,
+ });
} catch (error) {
console.error('Failed to delete hook:', error);
}