# Design Technique - Correction des Propriétés d'Étapes Vides **Auteur :** Dom, Alice, Kiro **Date :** 12 janvier 2026 **Version :** 1.0.0 ## Vue d'Ensemble de la Solution Cette solution corrige le problème des propriétés d'étapes vides en refactorisant la logique de détection et de mapping des types d'étapes dans le Visual Workflow Builder. ### Problème Diagnostiqué ```typescript // PROBLÈME ACTUEL : getParameterConfig() retourne [] const getParameterConfig = useCallback((): ParameterConfig[] => { if (!selectedStep) return []; return stepParametersConfig[selectedStep.type] || []; // ← Retourne toujours [] }, [selectedStep]); ``` **Cause racine** : Incohérence entre les types d'étapes créées et les clés de `stepParametersConfig`. ## Architecture de la Solution ### 1. Normalisation des Types d'Étapes ```typescript // Nouveau système de mapping unifié interface StepTypeResolver { resolveParameterConfig(step: Step): ParameterConfig[]; isVWBCatalogAction(step: Step): boolean; getVWBActionId(step: Step): string | null; normalizeStepType(stepType: string): StepType | null; } // Implémentation du résolveur class StepTypeResolverImpl implements StepTypeResolver { private readonly typeMapping = new Map(); private readonly vwbActionIds = new Set(); constructor() { this.initializeTypeMappings(); } private initializeTypeMappings() { // Mapping explicite des types d'étapes this.typeMapping.set('click', 'click'); this.typeMapping.set('type', 'type'); this.typeMapping.set('wait', 'wait'); // ... autres mappings // Actions VWB du catalogue this.vwbActionIds.add('click_anchor'); this.vwbActionIds.add('type_text'); this.vwbActionIds.add('type_secret'); // ... autres actions VWB } } ``` ### 2. Refactoring du PropertiesPanel ```typescript // Nouvelle logique de résolution des paramètres const PropertiesPanel: React.FC = ({ selectedStep, ... }) => { const stepTypeResolver = useStepTypeResolver(); const { vwbAction, isLoading } = useVWBActionDetails(selectedStep); // Résolution unifiée des paramètres const parameterConfig = useMemo(() => { if (!selectedStep) return []; // Log pour débogage console.log('🔍 Résolution paramètres pour étape:', { id: selectedStep.id, type: selectedStep.type, data: selectedStep.data }); return stepTypeResolver.resolveParameterConfig(selectedStep); }, [selectedStep, stepTypeResolver]); // Détection VWB améliorée const isVWBAction = useMemo(() => { if (!selectedStep) return false; return stepTypeResolver.isVWBCatalogAction(selectedStep); }, [selectedStep, stepTypeResolver]); // Rendu conditionnel amélioré return ( {/* Debug info en mode développement */} {process.env.NODE_ENV === 'development' && ( )} {/* Contenu principal */} {isVWBAction && vwbAction ? ( ) : parameterConfig.length > 0 ? ( ) : ( )} ); }; ``` ### 3. Amélioration des Hooks VWB ```typescript // Hook de résolution des types d'étapes export const useStepTypeResolver = (): StepTypeResolver => { return useMemo(() => new StepTypeResolverImpl(), []); }; // Hook amélioré pour les détails d'actions VWB export const useVWBActionDetails = (step: Step | null) => { const [vwbAction, setVwbAction] = useState(null); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const stepTypeResolver = useStepTypeResolver(); useEffect(() => { const loadVWBAction = async () => { if (!step || !stepTypeResolver.isVWBCatalogAction(step)) { setVwbAction(null); return; } const actionId = stepTypeResolver.getVWBActionId(step); if (!actionId) { setVwbAction(null); return; } setIsLoading(true); setError(null); try { // Essayer le catalogue dynamique d'abord let action = await catalogService.getActionDetails(actionId); // Fallback vers le catalogue statique if (!action) { action = getStaticActionById(actionId); } setVwbAction(action); console.log('✅ Action VWB chargée:', { actionId, actionName: action?.name, source: action ? 'catalogue' : 'statique' }); } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Erreur inconnue'; setError(errorMessage); console.error('❌ Erreur chargement action VWB:', err); } finally { setIsLoading(false); } }; loadVWBAction(); }, [step, stepTypeResolver]); return { vwbAction, isLoading, error }; }; ``` ### 4. Composant de Débogage ```typescript // Composant pour diagnostiquer les problèmes de mapping const DebugPanel: React.FC<{ step: Step | null; parameterConfig: ParameterConfig[]; isVWBAction: boolean; }> = ({ step, parameterConfig, isVWBAction }) => { if (!step) return null; return ( }> 🔧 Debug Info (Dev Mode) Step ID: {step.id} Step Type: {step.type} Is VWB Action: {isVWBAction ? 'Yes' : 'No'} Parameter Config Count: {parameterConfig.length} Step Data:
            {JSON.stringify(step.data, null, 2)}
          
Available Step Types in Config:
            {JSON.stringify(Object.keys(stepParametersConfig), null, 2)}
          
); }; ``` ### 5. Composant d'État Vide Amélioré ```typescript // Message d'état vide avec diagnostic const EmptyStateMessage: React.FC<{ stepType?: string }> = ({ stepType }) => { return ( Aucun paramètre configurable Cette étape n'a pas de paramètres configurables. {stepType && ( Type d'étape : {stepType} )} Si vous pensez que c'est une erreur, vérifiez la configuration des types d'étapes. ); }; ``` ## Stratégie de Migration ### Phase 1 : Diagnostic et Logging 1. Ajouter des logs détaillés dans `getParameterConfig()` 2. Créer le composant `DebugPanel` pour visualiser les problèmes 3. Identifier tous les cas où le mapping échoue ### Phase 2 : Refactoring Progressif 1. Créer `StepTypeResolver` avec tests unitaires 2. Migrer `PropertiesPanel` pour utiliser le nouveau résolveur 3. Améliorer les hooks VWB avec gestion d'erreurs ### Phase 3 : Validation et Optimisation 1. Tests d'intégration complets 2. Optimisation des performances avec mémorisation 3. Documentation et guides utilisateur ## Tests et Validation ### Tests Unitaires ```typescript describe('StepTypeResolver', () => { let resolver: StepTypeResolver; beforeEach(() => { resolver = new StepTypeResolverImpl(); }); describe('resolveParameterConfig', () => { it('should return click parameters for click step', () => { const step: Step = { id: 'test-1', type: 'click', name: 'Test Click', position: { x: 0, y: 0 }, data: {}, executionState: StepExecutionState.IDLE, validationErrors: [] }; const config = resolver.resolveParameterConfig(step); expect(config).toHaveLength(2); expect(config[0].name).toBe('target'); expect(config[1].name).toBe('clickType'); }); it('should detect VWB catalog actions', () => { const step: Step = { id: 'test-2', type: 'click_anchor', name: 'VWB Click', position: { x: 0, y: 0 }, data: { isVWBCatalogAction: true, vwbActionId: 'click_anchor' }, executionState: StepExecutionState.IDLE, validationErrors: [] }; const isVWB = resolver.isVWBCatalogAction(step); const actionId = resolver.getVWBActionId(step); expect(isVWB).toBe(true); expect(actionId).toBe('click_anchor'); }); }); }); ``` ### Tests d'Intégration ```typescript describe('PropertiesPanel Integration', () => { it('should display parameters for standard steps', async () => { const step: Step = createMockStep('type', { target: null, text: 'Hello World', clearFirst: true }); render( ); // Vérifier que les paramètres sont affichés expect(screen.getByLabelText('Champ de saisie')).toBeInTheDocument(); expect(screen.getByLabelText('Texte à saisir')).toBeInTheDocument(); expect(screen.getByLabelText('Vider le champ d\'abord')).toBeInTheDocument(); }); it('should display VWB action properties', async () => { const step: Step = createMockVWBStep('click_anchor'); render( ); // Attendre le chargement de l'action VWB await waitFor(() => { expect(screen.getByText('Cliquer sur Ancre')).toBeInTheDocument(); }); // Vérifier que le composant VWB est affiché expect(screen.getByText('Élément visuel à cliquer')).toBeInTheDocument(); }); }); ``` ## Performance et Optimisation ### Mémorisation des Résolutions ```typescript // Cache pour éviter les recalculs const stepTypeCache = new Map(); const resolveParameterConfigCached = (step: Step): ParameterConfig[] => { const cacheKey = `${step.type}_${step.data.isVWBCatalogAction}_${step.data.vwbActionId}`; if (stepTypeCache.has(cacheKey)) { return stepTypeCache.get(cacheKey)!; } const config = resolveParameterConfigInternal(step); stepTypeCache.set(cacheKey, config); return config; }; ``` ### Lazy Loading des Actions VWB ```typescript // Chargement différé des détails d'actions const useVWBActionDetailsLazy = (step: Step | null) => { const [shouldLoad, setShouldLoad] = useState(false); // Ne charger que quand l'étape est sélectionnée et visible useEffect(() => { if (step && stepTypeResolver.isVWBCatalogAction(step)) { setShouldLoad(true); } }, [step]); return useVWBActionDetails(shouldLoad ? step : null); }; ``` ## Monitoring et Observabilité ### Métriques de Performance ```typescript // Tracking des performances de résolution const performanceTracker = { trackResolution: (stepType: string, duration: number) => { console.log(`⏱️ Résolution ${stepType}: ${duration}ms`); // En production, envoyer à un service de monitoring if (process.env.NODE_ENV === 'production') { analytics.track('step_resolution_time', { stepType, duration, timestamp: Date.now() }); } } }; ``` ### Logs Structurés ```typescript // Système de logs pour diagnostic const logger = { debug: (message: string, context: any) => { if (process.env.NODE_ENV === 'development') { console.log(`🔍 [PropertiesPanel] ${message}`, context); } }, error: (message: string, error: Error, context: any) => { console.error(`❌ [PropertiesPanel] ${message}`, { error, context }); }, info: (message: string, context: any) => { console.log(`ℹ️ [PropertiesPanel] ${message}`, context); } }; ``` ## Sécurité et Robustesse ### Validation des Types ```typescript // Validation stricte des types d'étapes const validateStepType = (step: Step): boolean => { if (!step || !step.type) { logger.error('Step invalide', new Error('Step ou type manquant'), { step }); return false; } if (typeof step.type !== 'string') { logger.error('Type d\'étape invalide', new Error('Type doit être string'), { stepType: step.type, stepId: step.id }); return false; } return true; }; ``` ### Gestion d'Erreurs Gracieuse ```typescript // Fallback en cas d'erreur de résolution const resolveParameterConfigSafe = (step: Step): ParameterConfig[] => { try { if (!validateStepType(step)) { return []; } return resolveParameterConfig(step); } catch (error) { logger.error('Erreur résolution paramètres', error as Error, { stepId: step.id, stepType: step.type }); // Retourner une configuration minimale en cas d'erreur return [{ name: 'error_fallback', label: 'Configuration en erreur', type: 'text', required: false, description: 'Une erreur est survenue lors du chargement des paramètres.' }]; } }; ``` Cette architecture garantit une résolution robuste et performante des propriétés d'étapes, avec une observabilité complète pour faciliter le débogage et la maintenance.