/** * Hook for shared form state and validation logic used by TriggerCard and AddTriggerForm. */ import { useCallback, useState } from 'react'; import { api } from '@renderer/api'; import { useStore } from '@renderer/store'; import { createLogger } from '@shared/utils/logger'; const logger = createLogger('Component:TriggerForm'); import { generateId, validateRegexPattern } from '../utils/trigger'; import type { PreviewResult } from '../types'; import type { NotificationTrigger, TriggerMatchField, TriggerMode, TriggerTestResult, TriggerTokenType, } from '@renderer/types/data'; interface UseTriggerFormOptions { /** Initial trigger for editing mode, or undefined for new trigger creation */ trigger?: NotificationTrigger; /** Callback when trigger is updated (for edit mode) */ onUpdate?: (updates: Partial) => Promise; } interface UseTriggerFormReturn { // Pattern validation patternError: string | null; validatePattern: (pattern: string) => boolean; // Preview/test functionality previewResult: PreviewResult | null; handleTestTrigger: (trigger: NotificationTrigger) => Promise; handleViewSession: (error: TriggerTestResult['errors'][0]) => void; clearPreview: () => void; // Build trigger for testing (used by AddTriggerForm) buildTriggerForTest: (formState: { name: string; contentType: NotificationTrigger['contentType']; mode: TriggerMode; matchField?: string; matchPattern?: string; tokenThreshold?: number; tokenType?: TriggerTokenType; toolName?: string; ignorePatterns?: string[]; repositoryIds?: string[]; }) => NotificationTrigger; } /** * Shared form state and validation logic for trigger forms. */ export function useTriggerForm(_options: UseTriggerFormOptions = {}): UseTriggerFormReturn { const [patternError, setPatternError] = useState(null); const [previewResult, setPreviewResult] = useState(null); // Get navigateToError from store for View Session functionality const navigateToError = useStore((state) => state.navigateToError); /** * Validate a regex pattern. */ const validatePattern = useCallback((pattern: string): boolean => { const error = validateRegexPattern(pattern); setPatternError(error); return error === null; }, []); /** * Clear the preview result. */ const clearPreview = useCallback(() => { setPreviewResult(null); }, []); /** * Test trigger against historical data. * Results are automatically limited by the main process to prevent resource exhaustion: * - Max 50 errors returned * - Max 10,000 totalCount * - Max 100 sessions scanned * - 30 second timeout */ const handleTestTrigger = useCallback(async (trigger: NotificationTrigger) => { setPreviewResult({ loading: true, totalCount: 0, errors: [] }); try { const result = await api.config.testTrigger(trigger); setPreviewResult({ loading: false, totalCount: result.totalCount, errors: result.errors, truncated: result.truncated, }); } catch (error) { logger.error('Failed to test trigger:', error); setPreviewResult(null); } }, []); /** * Handle View Session click - navigate to the error location. */ const handleViewSession = useCallback( (error: TriggerTestResult['errors'][0]) => { navigateToError({ id: error.id, sessionId: error.sessionId, projectId: error.projectId, message: error.message, timestamp: error.timestamp, source: error.source, filePath: '', context: error.context, isRead: true, createdAt: error.timestamp, // Deep linking data for exact error position toolUseId: error.toolUseId, subagentId: error.subagentId, lineNumber: error.lineNumber, }); }, [navigateToError] ); /** * Build a trigger object from form state for testing. */ const buildTriggerForTest = useCallback( (formState: { name: string; contentType: NotificationTrigger['contentType']; mode: TriggerMode; matchField?: string; matchPattern?: string; tokenThreshold?: number; tokenType?: TriggerTokenType; toolName?: string; ignorePatterns?: string[]; repositoryIds?: string[]; }): NotificationTrigger => { return { id: `test-${generateId()}`, name: formState.name.trim() || 'Test Trigger', enabled: true, contentType: formState.contentType, mode: formState.mode, isBuiltin: false, ...(formState.mode === 'error_status' && { requireError: true }), ...(formState.mode === 'content_match' && formState.matchField && { matchField: formState.matchField as TriggerMatchField }), ...(formState.mode === 'content_match' && formState.matchPattern && { matchPattern: formState.matchPattern }), ...(formState.mode === 'token_threshold' && { tokenThreshold: formState.tokenThreshold, tokenType: formState.tokenType, }), ...((formState.contentType === 'tool_use' || formState.contentType === 'tool_result') && formState.toolName && { toolName: formState.toolName }), ...(formState.ignorePatterns && formState.ignorePatterns.length > 0 && { ignorePatterns: formState.ignorePatterns }), ...(formState.repositoryIds && formState.repositoryIds.length > 0 && { repositoryIds: formState.repositoryIds }), }; }, [] ); return { patternError, validatePattern, previewResult, handleTestTrigger, handleViewSession, clearPreview, buildTriggerForTest, }; }