diff --git a/visual_workflow_builder/frontend/src/App.tsx b/visual_workflow_builder/frontend/src/App.tsx
new file mode 100644
index 000000000..7d6cae8cc
--- /dev/null
+++ b/visual_workflow_builder/frontend/src/App.tsx
@@ -0,0 +1,630 @@
+/**
+ * Application principale Visual Workflow Builder V2
+ * Auteur : Dom, Alice, Kiro - 09 janvier 2026
+ *
+ * Interface moderne pour la création de workflows d'automatisation RPA
+ * avec sélection visuelle basée sur la vision et terminologie française.
+ */
+
+import { useState, useCallback, useMemo, useEffect, Component, ErrorInfo, ReactNode } from 'react';
+import {
+ Box,
+ CssBaseline,
+ ThemeProvider,
+ createTheme,
+ AppBar,
+ Toolbar,
+ Typography,
+ Tabs,
+ Tab,
+ Drawer,
+ Alert,
+ AlertTitle,
+ Button,
+} from '@mui/material';
+import { Provider } from 'react-redux';
+import { ReactFlowProvider } from '@xyflow/react';
+import { store } from './store';
+
+// Composants principaux
+import Canvas from './components/Canvas';
+import Palette from './components/Palette';
+import PropertiesPanel from './components/PropertiesPanel';
+import VariableManager from './components/VariableManager';
+import DocumentationTab from './components/DocumentationTab';
+import Validator from './components/Validator';
+import Executor from './components/Executor';
+import WorkflowManager from './components/WorkflowManager';
+import ContextualHelp from './components/ContextualHelp';
+import KeyboardShortcuts from './components/KeyboardShortcuts';
+import AccessibilityProvider from './components/AccessibilityProvider';
+import ConnectionIndicator from './components/ConnectionIndicator';
+import TestCatalogLoader from './components/TestCatalogLoader';
+import VWBIntegrationTest from './components/VWBIntegrationTest';
+
+// Hooks personnalisés
+import { useKeyboardNavigation } from './hooks/useKeyboardNavigation';
+import { useResponsiveLayout } from './hooks/useResponsiveLayout';
+
+// Import des types partagés
+import {
+ Step,
+ Variable,
+ StepTemplate,
+ StepCategory,
+ Position,
+ Workflow,
+ WorkflowConnection,
+ StepExecutionState,
+} from './types';
+
+// Error Boundary pour gérer les erreurs de l'application
+interface ErrorBoundaryState {
+ hasError: boolean;
+ error?: Error;
+ errorInfo?: ErrorInfo;
+}
+
+class AppErrorBoundary extends Component<
+ { children: ReactNode },
+ ErrorBoundaryState
+> {
+ constructor(props: { children: ReactNode }) {
+ super(props);
+ this.state = { hasError: false };
+ }
+
+ static getDerivedStateFromError(error: Error): ErrorBoundaryState {
+ // Met à jour l'état pour afficher l'interface d'erreur
+ return { hasError: true, error };
+ }
+
+ componentDidCatch(error: Error, errorInfo: ErrorInfo) {
+ // Log l'erreur pour le debugging
+ console.error('Erreur capturée par Error Boundary:', error, errorInfo);
+ this.setState({
+ error,
+ errorInfo,
+ });
+ }
+
+ render() {
+ if (this.state.hasError) {
+ return (
+
+
+ Une erreur inattendue s'est produite
+
+ L'application a rencontré une erreur et ne peut pas continuer.
+
+ {this.state.error && (
+
+ {this.state.error.message}
+
+ )}
+
+
+
+ );
+ }
+
+ return this.props.children;
+ }
+}
+
+// Thème Material-UI personnalisé avec couleurs françaises - mémorisé pour éviter les re-créations
+const theme = createTheme({
+ palette: {
+ primary: {
+ main: '#1976d2', // Bleu français
+ },
+ secondary: {
+ main: '#dc004e', // Rouge français
+ },
+ background: {
+ default: '#fafafa',
+ paper: '#ffffff',
+ },
+ },
+ typography: {
+ fontFamily: '"Segoe UI", "Roboto", "Helvetica", "Arial", sans-serif',
+ },
+ components: {
+ MuiAppBar: {
+ styleOverrides: {
+ root: {
+ backgroundColor: '#ffffff',
+ color: '#1976d2',
+ boxShadow: '0 1px 3px rgba(0,0,0,0.12)',
+ },
+ },
+ },
+ },
+});
+
+/**
+ * Composant principal de l'application
+ */
+function App() {
+ // États principaux
+ const [selectedStep, setSelectedStep] = useState(null);
+ const [variables, setVariables] = useState([]);
+ const [searchTerm, setSearchTerm] = useState('');
+ const [activeDocTab, setActiveDocTab] = useState(0);
+ const [isDocDrawerOpen, setIsDocDrawerOpen] = useState(false);
+ const [showKeyboardShortcuts, setShowKeyboardShortcuts] = useState(false);
+
+ // Hooks d'accessibilité et responsivité
+ const responsiveLayout = useResponsiveLayout();
+
+ // État du workflow
+ const [workflow, setWorkflow] = useState({
+ id: 'workflow_1',
+ name: 'Nouveau Workflow',
+ description: 'Workflow créé avec le Visual Workflow Builder V2',
+ steps: [],
+ connections: [],
+ variables: [],
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ });
+
+ // Configuration de la navigation au clavier
+ const keyboardNavigation = useKeyboardNavigation({
+ onStepSelect: (stepId: string) => {
+ const step = workflow.steps.find(s => s.id === stepId);
+ if (step) handleStepSelect(step);
+ },
+ onStepMove: (stepId: string, direction: 'up' | 'down' | 'left' | 'right') => {
+ const step = workflow.steps.find(s => s.id === stepId);
+ if (step) {
+ const moveDistance = 20;
+ const newPosition = { ...step.position };
+
+ switch (direction) {
+ case 'up': newPosition.y -= moveDistance; break;
+ case 'down': newPosition.y += moveDistance; break;
+ case 'left': newPosition.x -= moveDistance; break;
+ case 'right': newPosition.x += moveDistance; break;
+ }
+
+ handleStepMove(stepId, newPosition);
+ }
+ },
+ onStepDelete: (stepId: string) => handleStepDelete(stepId),
+ onSave: () => console.log('Sauvegarde via clavier'),
+ onHelp: () => setShowKeyboardShortcuts(true),
+ selectedStepId: selectedStep?.id,
+ availableStepIds: workflow.steps.map(s => s.id),
+ isEnabled: true,
+ });
+
+ // Gestionnaires d'événements optimisés avec useCallback
+ const handleStepSelect = useCallback((step: Step | null) => {
+ setSelectedStep(step);
+ }, []);
+
+ // Synchroniser selectedStep avec le workflow quand celui-ci change
+ // Ceci corrige le bug où les paramètres (visual_anchor) ne sont pas persistés dans l'UI
+ useEffect(() => {
+ if (selectedStep) {
+ const updatedStep = workflow.steps.find(s => s.id === selectedStep.id);
+ if (updatedStep) {
+ // Vérifier si les données ont changé avant de mettre à jour
+ const currentDataStr = JSON.stringify(selectedStep.data);
+ const updatedDataStr = JSON.stringify(updatedStep.data);
+ if (currentDataStr !== updatedDataStr) {
+ setSelectedStep(updatedStep);
+ }
+ }
+ }
+ }, [workflow.steps, selectedStep?.id]);
+
+ const handleStepMove = useCallback((stepId: string, position: Position) => {
+ console.log('Déplacement étape:', stepId, position);
+ setWorkflow(prev => ({
+ ...prev,
+ steps: prev.steps.map(step =>
+ step.id === stepId ? { ...step, position } : step
+ ),
+ updatedAt: new Date(),
+ }));
+ }, []);
+
+ const handleConnection = (source: string, target: string) => {
+ console.log('Connexion:', source, '->', target);
+ const newConnection: WorkflowConnection = {
+ id: `conn_${Date.now()}`,
+ source,
+ target,
+ };
+ setWorkflow(prev => ({
+ ...prev,
+ connections: [...prev.connections, newConnection],
+ updatedAt: new Date(),
+ }));
+ };
+
+ const handleStepAdd = (stepData: Omit) => {
+ console.log('Ajout étape:', stepData);
+ const newStep: Step = {
+ ...stepData,
+ id: `step_${Date.now()}`,
+ executionState: StepExecutionState.IDLE,
+ validationErrors: [],
+ };
+
+ setWorkflow(prev => ({
+ ...prev,
+ steps: [...prev.steps, newStep],
+ updatedAt: new Date(),
+ }));
+ };
+
+ const handleStepDelete = (stepId: string) => {
+ console.log('Suppression étape:', stepId);
+ setWorkflow(prev => ({
+ ...prev,
+ steps: prev.steps.filter(step => step.id !== stepId),
+ connections: prev.connections.filter(conn =>
+ conn.source !== stepId && conn.target !== stepId
+ ),
+ updatedAt: new Date(),
+ }));
+
+ // Désélectionner l'étape si elle était sélectionnée
+ if (selectedStep?.id === stepId) {
+ setSelectedStep(null);
+ }
+ };
+
+ const handleConnectionDelete = (connectionId: string) => {
+ console.log('Suppression connexion:', connectionId);
+ setWorkflow(prev => ({
+ ...prev,
+ connections: prev.connections.filter(conn => conn.id !== connectionId),
+ updatedAt: new Date(),
+ }));
+ };
+
+ const handleParameterChange = (stepId: string, param: string, value: any) => {
+ console.log('Changement paramètre:', stepId, param, value);
+ setWorkflow(prev => ({
+ ...prev,
+ steps: prev.steps.map(step =>
+ step.id === stepId
+ ? {
+ ...step,
+ data: {
+ ...step.data,
+ parameters: {
+ ...step.data.parameters,
+ [param]: value
+ }
+ }
+ }
+ : step
+ ),
+ updatedAt: new Date(),
+ }));
+ };
+
+ const handleVisualSelection = (stepId: string) => {
+ console.log('Sélection visuelle pour étape:', stepId);
+ // La sélection visuelle est maintenant gérée par le VisualSelector dans PropertiesPanel
+ };
+
+ const handleStepHighlight = (stepId: string, highlight: boolean) => {
+ // Mise en évidence des étapes pour la validation
+ console.log('Mise en évidence étape:', stepId, highlight ? 'activée' : 'désactivée');
+ };
+
+ const handleStepStateChange = (stepId: string, state: StepExecutionState) => {
+ setWorkflow(prev => ({
+ ...prev,
+ steps: prev.steps.map(step =>
+ step.id === stepId ? { ...step, executionState: state } : step
+ ),
+ updatedAt: new Date(),
+ }));
+ };
+
+ const handleExecutionComplete = (success: boolean, summary: any) => {
+ console.log('Exécution terminée:', success ? 'Succès' : 'Échec', summary);
+ };
+
+ const handleWorkflowLoad = (loadedWorkflow: Workflow) => {
+ console.log('📥 [App] Chargement workflow:', {
+ name: loadedWorkflow.name,
+ stepsCount: loadedWorkflow.steps?.length || 0,
+ connectionsCount: loadedWorkflow.connections?.length || 0,
+ steps: loadedWorkflow.steps,
+ });
+
+ // S'assurer que les étapes ont des positions valides
+ const workflowWithPositions: Workflow = {
+ ...loadedWorkflow,
+ steps: (loadedWorkflow.steps || []).map((step, index) => ({
+ ...step,
+ position: step.position || { x: 100 + (index % 3) * 200, y: 100 + Math.floor(index / 3) * 150 },
+ })),
+ };
+
+ setWorkflow(workflowWithPositions);
+ // Charger les variables du workflow
+ setVariables(workflowWithPositions.variables || []);
+ setSelectedStep(null);
+ console.log('✅ [App] Workflow appliqué avec', workflowWithPositions.steps.length, 'étapes et', (workflowWithPositions.variables || []).length, 'variables');
+ };
+
+ const handleWorkflowSave = (savedWorkflow: Workflow) => {
+ setWorkflow(savedWorkflow);
+ console.log('Workflow sauvegardé:', savedWorkflow.name);
+ };
+
+ const handleStepDrag = (stepTemplate: StepTemplate) => {
+ console.log('Début drag étape:', stepTemplate);
+ // Cette fonction est appelée quand le drag commence depuis la palette
+ // La logique de drop est gérée dans le Canvas
+ };
+
+ // Gestion des variables
+ const handleVariableCreate = (variable: Omit) => {
+ const newVariable: Variable = {
+ ...variable,
+ id: `var_${Date.now()}`,
+ };
+ const updatedVariables = [...variables, newVariable];
+ setVariables(updatedVariables);
+ // Synchroniser avec le workflow pour la sauvegarde
+ setWorkflow(prev => ({ ...prev, variables: updatedVariables, updatedAt: new Date() }));
+ };
+
+ const handleVariableUpdate = (id: string, updates: Partial) => {
+ const updatedVariables = variables.map(v => v.id === id ? { ...v, ...updates } : v);
+ setVariables(updatedVariables);
+ // Synchroniser avec le workflow pour la sauvegarde
+ setWorkflow(prev => ({ ...prev, variables: updatedVariables, updatedAt: new Date() }));
+ };
+
+ const handleVariableDelete = (id: string) => {
+ const updatedVariables = variables.filter(v => v.id !== id);
+ setVariables(updatedVariables);
+ // Synchroniser avec le workflow pour la sauvegarde
+ setWorkflow(prev => ({ ...prev, variables: updatedVariables, updatedAt: new Date() }));
+ };
+
+ // Onglets de documentation
+ const docTabs = [
+ { id: 'canvas', label: 'Canvas', toolName: 'canvas' },
+ { id: 'palette', label: 'Palette', toolName: 'palette' },
+ { id: 'properties', label: 'Propriétés', toolName: 'properties' },
+ { id: 'test', label: 'Test Catalogue', toolName: 'test' },
+ { id: 'vwb-test', label: 'Test VWB', toolName: 'vwb-test' },
+ ];
+
+ // Mode 100% Visuel - Pas de catégories par défaut
+ // Seules les actions VisionOnly du catalogue sont utilisées
+ const defaultCategories: StepCategory[] = [];
+
+ return (
+
+
+
+
+
+
+
+ {/* Barre d'application */}
+
+
+
+ Visual Workflow Builder V2
+
+
+ {/* Gestionnaire de workflows */}
+
+
+
+
+ {/* Indicateur de connexion API */}
+
+
+
+
+ {
+ setActiveDocTab(newValue);
+ setIsDocDrawerOpen(true);
+ }}
+ textColor="inherit"
+ indicatorColor="secondary"
+ >
+
+
+
+
+
+
+
+
+
+ {/* Interface principale */}
+
+ {/* Palette d'étapes - Responsive */}
+
+
+
+
+ {/* Canvas principal */}
+
+ {/* Validateur et Exécuteur en haut */}
+
+ {/* Validateur */}
+
+
+
+
+ {/* Exécuteur */}
+
+ 0}
+ onStepStateChange={handleStepStateChange}
+ onExecutionComplete={handleExecutionComplete}
+ />
+
+
+
+
+
+ {/* Gestionnaire de variables - Responsive */}
+
+
+
+
+
+ {/* Panneau de propriétés - Responsive */}
+
+
+
+
+
+ {/* Tiroir de documentation */}
+ setIsDocDrawerOpen(false)}
+ sx={{
+ '& .MuiDrawer-paper': {
+ width: responsiveLayout.isMobile ? '100%' : 400,
+ boxSizing: 'border-box',
+ },
+ }}
+ >
+ {activeDocTab === 3 ? (
+
+ ) : activeDocTab === 4 ? (
+
+ ) : (
+ setIsDocDrawerOpen(true)}
+ />
+ )}
+
+
+ {/* Aide contextuelle */}
+
+
+ {/* Dialogue des raccourcis clavier */}
+ setShowKeyboardShortcuts(false)}
+ shortcuts={keyboardNavigation.shortcuts}
+ />
+
+
+
+
+
+
+ );
+}
+
+export default App;
\ No newline at end of file
diff --git a/visual_workflow_builder/frontend/src/components/Executor/index.tsx b/visual_workflow_builder/frontend/src/components/Executor/index.tsx
new file mode 100644
index 000000000..eab79ca42
--- /dev/null
+++ b/visual_workflow_builder/frontend/src/components/Executor/index.tsx
@@ -0,0 +1,1015 @@
+/**
+ * Composant Exécuteur - Exécution et feedback temps réel des workflows
+ * Auteur : Dom, Alice, Kiro - 10 janvier 2026
+ *
+ * Ce composant gère l'exécution des workflows avec feedback visuel en temps réel,
+ * affichage des états d'exécution, résumé des performances et gestion robuste des erreurs.
+ *
+ * EXTENSION VWB : Support complet des actions VisionOnly avec Evidence et validation.
+ */
+
+import React, { useState, useCallback, useEffect } from 'react';
+import {
+ Box,
+ Button,
+ Typography,
+ LinearProgress,
+ Alert,
+ AlertTitle,
+ List,
+ ListItem,
+ ListItemIcon,
+ ListItemText,
+ Chip,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ IconButton,
+ Collapse,
+ Snackbar,
+ CircularProgress,
+ Tabs,
+ Tab,
+ Badge,
+} from '@mui/material';
+import {
+ PlayArrow as PlayIcon,
+ Stop as StopIcon,
+ Pause as PauseIcon,
+ CheckCircle as SuccessIcon,
+ Error as ErrorIcon,
+ Schedule as PendingIcon,
+ Close as CloseIcon,
+ ExpandMore as ExpandMoreIcon,
+ ExpandLess as ExpandLessIcon,
+ Refresh as RefreshIcon,
+ CloudOff as OfflineIcon,
+ Visibility as VWBIcon,
+ Speed as StandardIcon,
+} from '@mui/icons-material';
+
+// Import des types partagés et du nouveau client API
+import {
+ Workflow,
+ Step,
+ StepExecutionState,
+ ExecutionState,
+ ExecutionError,
+ Variable,
+ Evidence,
+} from '../../types';
+import { useWorkflowExecution } from '../../hooks/useApiClient';
+import { useConnectionStatus } from '../../hooks/useConnectionStatus';
+import { ApiError } from '../../services/apiClient';
+
+// Import des composants et services VWB
+import VWBExecutorExtension from './VWBExecutorExtension';
+import { useVWBExecutionService } from '../../services/vwbExecutionService';
+import { VWBExecutionSummary } from '../../hooks/useVWBExecution';
+
+interface ExecutorProps {
+ workflow: Workflow;
+ variables?: Variable[];
+ canExecute: boolean;
+ onStepStateChange?: (stepId: string, state: StepExecutionState) => void;
+ onExecutionComplete?: (success: boolean, summary: ExecutionSummary | VWBExecutionSummary) => void;
+ onEvidenceGenerated?: (stepId: string, evidence: Evidence[]) => void;
+ enableVWBMode?: boolean;
+ debugMode?: boolean;
+}
+
+interface ExecutionSummary {
+ totalSteps: number;
+ successfulSteps: number;
+ failedSteps: number;
+ skippedSteps: number;
+ duration: number;
+ successRate: number;
+ errors: ExecutionError[];
+}
+
+interface StepExecutionResult {
+ stepId: string;
+ success: boolean;
+ duration: number;
+ error?: string;
+ output?: any;
+}
+
+/**
+ * Composant Exécuteur avec Support VWB
+ */
+const Executor: React.FC = ({
+ workflow,
+ variables = [],
+ canExecute,
+ onStepStateChange,
+ onExecutionComplete,
+ onEvidenceGenerated,
+ enableVWBMode = true,
+ debugMode = false,
+}) => {
+ // État pour le mode d'exécution
+ const [executionMode, setExecutionMode] = useState<'standard' | 'vwb'>('standard');
+
+ // Service VWB pour détecter les actions VWB
+ const { isVWBStep } = useVWBExecutionService();
+
+ // Détecter automatiquement si le workflow contient des actions VWB
+ const hasVWBSteps = React.useMemo(() => {
+ return workflow.steps.some(step => isVWBStep(step));
+ }, [workflow.steps, isVWBStep]);
+
+ // Définir le mode d'exécution par défaut
+ React.useEffect(() => {
+ if (enableVWBMode && hasVWBSteps) {
+ setExecutionMode('vwb');
+ } else {
+ setExecutionMode('standard');
+ }
+ }, [enableVWBMode, hasVWBSteps]);
+
+ // Si le mode VWB est activé et qu'il y a des actions VWB, utiliser l'extension VWB
+ if (executionMode === 'vwb' && enableVWBMode) {
+ return (
+
+ {/* Sélecteur de mode d'exécution */}
+ {hasVWBSteps && (
+
+ setExecutionMode(newValue)}
+ aria-label="Mode d'exécution"
+ >
+
+
+ Standard
+
+ }
+ />
+
+
+ VWB
+ isVWBStep(step)).length} color="primary" />
+
+ }
+ />
+
+
+ )}
+
+ {/* Extension VWB */}
+
+
+ );
+ }
+
+ // Mode d'exécution standard (code existant)
+ return (
+
+ );
+};
+
+/**
+ * Composant Exécuteur Standard (code existant refactorisé)
+ */
+interface StandardExecutorProps {
+ workflow: Workflow;
+ variables: Variable[];
+ canExecute: boolean;
+ onStepStateChange?: (stepId: string, state: StepExecutionState) => void;
+ onExecutionComplete?: (success: boolean, summary: ExecutionSummary | VWBExecutionSummary) => void;
+ executionMode: 'standard' | 'vwb';
+ setExecutionMode: (mode: 'standard' | 'vwb') => void;
+ hasVWBSteps: boolean;
+ enableVWBMode: boolean;
+}
+
+const StandardExecutor: React.FC = ({
+ workflow,
+ variables,
+ canExecute,
+ onStepStateChange,
+ onExecutionComplete,
+ executionMode,
+ setExecutionMode,
+ hasVWBSteps,
+ enableVWBMode,
+}) => {
+ // Utilisation du nouveau client API pour l'exécution
+ const executionApi = useWorkflowExecution({
+ onError: (error: ApiError) => {
+ console.error('Erreur API Exécution:', error);
+ setSnackbarMessage(`Erreur d'exécution: ${error.message}`);
+ setSnackbarOpen(true);
+ handleExecutionError(error.message);
+ },
+ onSuccess: (data) => {
+ console.log('Succès API Exécution:', data);
+ },
+ silentOffline: true, // Ne pas afficher d'erreur en mode hors ligne
+ });
+
+ // Surveillance de l'état de connexion (sans polling pour éviter les re-renders)
+ const { isOffline } = useConnectionStatus();
+
+ const [executionState, setExecutionState] = useState({
+ status: 'idle',
+ });
+ // Ref pour suivre l'état d'exécution dans les closures
+ const executionRef = React.useRef<{
+ isRunning: boolean;
+ isPaused: boolean;
+ shouldStop: boolean;
+ }>({
+ isRunning: false,
+ isPaused: false,
+ shouldStop: false,
+ });
+ const [currentStepIndex, setCurrentStepIndex] = useState(0);
+ const [stepResults, setStepResults] = useState([]);
+ const [showSummaryDialog, setShowSummaryDialog] = useState(false);
+ const [executionSummary, setExecutionSummary] = useState(null);
+ const [expandedErrors, setExpandedErrors] = useState(false);
+ const [snackbarOpen, setSnackbarOpen] = useState(false);
+ const [snackbarMessage, setSnackbarMessage] = useState('');
+ const [retryAttempts, setRetryAttempts] = useState