/** * Specs Settings Page * * Main page for managing spec settings, hooks, injection control, and global settings. * Uses 5 tabs: Project Specs | Personal Specs | Hooks | Injection | Settings */ import { useState } from 'react'; import { useIntl } from 'react-intl'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { ScrollText, User, Plug, Gauge, Settings, RefreshCw, Search } from 'lucide-react'; import { SpecCard, SpecDialog, type Spec, type SpecFormData } from '@/components/specs'; import { HookCard, HookDialog, type HookConfig } from '@/components/specs'; import { InjectionControlTab } from '@/components/specs/InjectionControlTab'; import { GlobalSettingsTab } from '@/components/specs/GlobalSettingsTab'; import { useSpecStats } from '@/hooks/useSystemSettings'; type SettingsTab = 'project-specs' | 'personal-specs' | 'hooks' | 'injection' | 'settings'; export function SpecsSettingsPage() { const { formatMessage } = useIntl(); const [activeTab, setActiveTab] = useState('project-specs'); const [searchQuery, setSearchQuery] = useState(''); const [editDialogOpen, setEditDialogOpen] = useState(false); const [hookDialogOpen, setHookDialogOpen] = useState(false); const [editingSpec, setEditingSpec] = useState(null); const [editingHook, setEditingHook] = useState(null); // Mock data for demonstration - will be replaced with real API calls const [projectSpecs] = useState([]); const [personalSpecs] = useState([]); const [hooks] = useState([]); const [isLoading] = useState(false); const { data: statsData, refetch: refetchStats } = useSpecStats(); const handleSpecEdit = (spec: Spec) => { setEditingSpec(spec); setEditDialogOpen(true); }; const handleSpecSave = async (specId: string, data: SpecFormData) => { console.log('Saving spec:', specId, data); // TODO: Implement save logic setEditDialogOpen(false); }; const handleSpecToggle = async (specId: string, enabled: boolean) => { console.log('Toggling spec:', specId, enabled); // TODO: Implement toggle logic }; const handleSpecDelete = async (specId: string) => { console.log('Deleting spec:', specId); // TODO: Implement delete logic }; const handleHookEdit = (hook: HookConfig) => { setEditingHook(hook); setHookDialogOpen(true); }; const handleHookSave = async (hookId: string | null, data: Partial) => { console.log('Saving hook:', hookId, data); // TODO: Implement save logic setHookDialogOpen(false); }; const handleHookToggle = async (hookId: string, enabled: boolean) => { console.log('Toggling hook:', hookId, enabled); // TODO: Implement toggle logic }; const handleHookDelete = async (hookId: string) => { console.log('Deleting hook:', hookId); // TODO: Implement delete logic }; const handleRebuildIndex = async () => { console.log('Rebuilding index...'); // TODO: Implement rebuild logic }; const filterSpecs = (specs: Spec[]) => { if (!searchQuery.trim()) return specs; const query = searchQuery.toLowerCase(); return specs.filter(spec => spec.title.toLowerCase().includes(query) || spec.keywords.some(k => k.toLowerCase().includes(query)) ); }; const renderSpecsTab = (dimension: 'project' | 'personal') => { const specs = dimension === 'project' ? projectSpecs : personalSpecs; const filteredSpecs = filterSpecs(specs); return (
{/* Search and Actions */}
setSearchQuery(e.target.value)} className="pl-9" />
{/* Stats Summary */} {statsData && (
{Object.entries(statsData.dimensions).map(([dim, data]) => (
{dim}
{(data as { count: number }).count}
{(data as { requiredCount: number }).requiredCount} required
))}
)} {/* Specs Grid */} {filteredSpecs.length === 0 ? ( {isLoading ? formatMessage({ id: 'specs.loading', defaultMessage: 'Loading specs...' }) : formatMessage({ id: 'specs.noSpecs', defaultMessage: 'No specs found. Create specs in .workflow/ directory.' }) } ) : (
{filteredSpecs.map(spec => ( ))}
)}
); }; const renderHooksTab = () => { const filteredHooks = hooks.filter(hook => { if (!searchQuery.trim()) return true; const query = searchQuery.toLowerCase(); return hook.name.toLowerCase().includes(query) || hook.event.toLowerCase().includes(query); }); // Recommended hooks const recommendedHooks: HookConfig[] = [ { id: 'spec-injection-session', name: 'Spec Context Injection (Session)', event: 'SessionStart', command: 'ccw spec load --stdin', scope: 'global', enabled: true, timeout: 5000, failMode: 'silent' }, { id: 'spec-injection-prompt', name: 'Spec Context Injection (Prompt)', event: 'UserPromptSubmit', command: 'ccw spec load --stdin', scope: 'project', enabled: true, timeout: 5000, failMode: 'silent' } ]; return (
{/* Recommended Hooks Section */} {formatMessage({ id: 'specs.recommendedHooks', defaultMessage: 'Recommended Hooks' })} {formatMessage({ id: 'specs.recommendedHooksDesc', defaultMessage: 'One-click install system-preset spec injection hooks' })}
{hooks.filter(h => recommendedHooks.some(r => r.command === h.command)).length} / {recommendedHooks.length} installed
{recommendedHooks.map(hook => ( console.log('Install:', hook.id)} onEdit={handleHookEdit} onToggle={handleHookToggle} onDelete={handleHookDelete} /> ))}
{/* Installed Hooks Section */} {formatMessage({ id: 'specs.installedHooks', defaultMessage: 'Installed Hooks' })} {formatMessage({ id: 'specs.installedHooksDesc', defaultMessage: 'Manage your installed hooks configuration' })}
setSearchQuery(e.target.value)} className="pl-9" />
{filteredHooks.length === 0 ? (
{formatMessage({ id: 'specs.noHooks', defaultMessage: 'No hooks installed. Install recommended hooks above.' })}
) : (
{filteredHooks.map(hook => ( ))}
)}
); }; return (
{/* Page Header */}

{formatMessage({ id: 'specs.pageTitle', defaultMessage: 'Spec Settings' })}

{formatMessage({ id: 'specs.pageDescription', defaultMessage: 'Manage specification injection, hooks, and system settings' })}

{/* Main Tabs */} setActiveTab(v as SettingsTab)}> {formatMessage({ id: 'specs.tabProjectSpecs', defaultMessage: 'Project Specs' })} {formatMessage({ id: 'specs.tabPersonalSpecs', defaultMessage: 'Personal' })} {formatMessage({ id: 'specs.tabHooks', defaultMessage: 'Hooks' })} {formatMessage({ id: 'specs.tabInjection', defaultMessage: 'Injection' })} {formatMessage({ id: 'specs.tabSettings', defaultMessage: 'Settings' })} {renderSpecsTab('project')} {renderSpecsTab('personal')} {renderHooksTab()} {/* Edit Spec Dialog */} {/* Edit Hook Dialog */}
); } export default SpecsSettingsPage;