# Web UI Architecture

**Created:** 2024-01-18  
**Status:** Planning  
**Tech Stack:** React 18 + TypeScript + Vite

## Overview

The Attune Web UI is a single-page application (SPA) that provides a comprehensive interface for managing and monitoring the Attune automation platform. It communicates with the Attune API service via REST endpoints and receives real-time updates through WebSocket connections to the notifier service.

## Architecture Principles

1. **Type Safety**: Full TypeScript coverage with types generated from OpenAPI specification
2. **Real-time Updates**: WebSocket integration for live execution monitoring and notifications
3. **Offline-first Caching**: Intelligent client-side caching with optimistic updates
4. **Component Reusability**: Shared components for common patterns (lists, forms, detail views)
5. **Performance**: Code splitting, lazy loading, and efficient re-rendering
6. **Developer Experience**: Fast dev server, hot module replacement, clear error messages

## Technology Stack

### Core Framework

#### **React 18**
- **Purpose**: UI component library and rendering engine
- **Why**: Industry standard, mature ecosystem, excellent for complex UIs
- **Key Features Used**:
  - Hooks for state management (useState, useEffect, useContext)
  - Suspense for code splitting and async data loading
  - Concurrent rendering for improved UX
  - Error boundaries for graceful error handling

#### **TypeScript 5.x**
- **Purpose**: Type safety and improved developer experience
- **Why**: Catches errors at compile time, better IDE support, self-documenting code
- **Configuration**:
  - Strict mode enabled
  - Path aliases for clean imports (@/components, @/api, @/hooks)
  - Integration with OpenAPI-generated types

#### **Vite**
- **Purpose**: Build tool and dev server
- **Why**: Fast HMR, optimized production builds, native ESM support
- **Features**:
  - Sub-second dev server startup
  - Instant hot module replacement
  - Optimized production builds with Rollup
  - Environment variable management
  - Plugin ecosystem (React, TypeScript support out of the box)

### API Layer

#### **OpenAPI TypeScript Codegen**
- **Purpose**: Generate type-safe API client from OpenAPI spec
- **Why**: Single source of truth, automatic updates when API changes
- **Generated Artifacts**:
  - TypeScript interfaces for all DTOs (requests/responses)
  - API client with methods for all 86 endpoints
  - Type-safe parameter validation
  - Automatic bearer token injection

**Usage Pattern**:
```typescript
// Generated client usage
import { ActionsService } from '@/api/services/ActionsService';

// Type-safe API call with auto-complete
const actions = await ActionsService.listActions({
  limit: 50,
  offset: 0
});
// actions is typed as ApiResponse<PaginatedResponse<ActionSummary[]>>
```

**Code Generation Command**:
```bash
# Run whenever API spec changes
npm run generate:api

# Implemented as:
openapi-typescript-codegen \
  --input http://localhost:8080/api-spec/openapi.json \
  --output ./src/api \
  --client axios \
  --useOptions
```

**Configuration** (`openapi-codegen.config.json`):
- Client: axios (configurable for auth, interceptors)
- Type generation: all request/response types
- Service generation: one service class per tag (PacksService, ActionsService, etc.)
- Enum handling: TypeScript enums for all OpenAPI enums

#### **Axios**
- **Purpose**: HTTP client for API requests
- **Why**: Interceptors for auth, request/response transformation, browser/node compatibility
- **Configuration**:
  - Base URL from environment variable
  - Request interceptor: inject JWT token from storage
  - Response interceptor: handle 401 (refresh token), network errors
  - Timeout configuration
  - Request/response logging in development

**Axios Instance Setup**:
```typescript
// src/api/client.ts
import axios from 'axios';
import { getAccessToken, refreshAccessToken } from '@/auth/tokens';

const apiClient = axios.create({
  baseURL: import.meta.env.VITE_API_URL || 'http://localhost:8080',
  timeout: 30000,
  headers: {
    'Content-Type': 'application/json',
  },
});

// Request interceptor: inject auth token
apiClient.interceptors.request.use((config) => {
  const token = getAccessToken();
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

// Response interceptor: handle auth errors
apiClient.interceptors.response.use(
  (response) => response,
  async (error) => {
    const originalRequest = error.config;
    
    // If 401 and not already retried, attempt token refresh
    if (error.response?.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;
      try {
        await refreshAccessToken();
        return apiClient(originalRequest);
      } catch (refreshError) {
        // Refresh failed, redirect to login
        window.location.href = '/login';
        return Promise.reject(refreshError);
      }
    }
    
    return Promise.reject(error);
  }
);
```

### Data Fetching & Caching

#### **TanStack Query (React Query v5)**
- **Purpose**: Server state management, caching, and synchronization
- **Why**: Eliminates boilerplate, automatic caching, background refetching, optimistic updates
- **Key Features**:
  - Automatic background refetching
  - Cache invalidation and updates
  - Optimistic updates for better UX
  - Request deduplication
  - Pagination and infinite scroll support
  - Prefetching for improved perceived performance

**Query Usage Patterns**:

```typescript
// src/hooks/useActions.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { ActionsService } from '@/api/services/ActionsService';

// Query for list of actions
export function useActions(params?: { pack_ref?: string; limit?: number }) {
  return useQuery({
    queryKey: ['actions', params],
    queryFn: () => ActionsService.listActions(params),
    staleTime: 30000, // Consider fresh for 30s
    gcTime: 5 * 60 * 1000, // Keep in cache for 5 min
  });
}

// Query for single action
export function useAction(ref: string) {
  return useQuery({
    queryKey: ['actions', ref],
    queryFn: () => ActionsService.getActionByRef({ ref }),
    enabled: !!ref, // Only fetch if ref is provided
  });
}

// Mutation for creating action
export function useCreateAction() {
  const queryClient = useQueryClient();
  
  return useMutation({
    mutationFn: ActionsService.createAction,
    onSuccess: (newAction) => {
      // Invalidate action list to trigger refetch
      queryClient.invalidateQueries({ queryKey: ['actions'] });
      
      // Optimistically update cache with new action
      queryClient.setQueryData(['actions', newAction.ref], newAction);
    },
  });
}

// Mutation with optimistic update
export function useUpdateAction() {
  const queryClient = useQueryClient();
  
  return useMutation({
    mutationFn: ({ ref, data }: { ref: string; data: UpdateActionRequest }) =>
      ActionsService.updateAction({ ref, requestBody: data }),
    onMutate: async ({ ref, data }) => {
      // Cancel outgoing refetches
      await queryClient.cancelQueries({ queryKey: ['actions', ref] });
      
      // Snapshot previous value
      const previous = queryClient.getQueryData(['actions', ref]);
      
      // Optimistically update
      queryClient.setQueryData(['actions', ref], (old: any) => ({
        ...old,
        ...data,
      }));
      
      return { previous };
    },
    onError: (err, variables, context) => {
      // Rollback on error
      queryClient.setQueryData(['actions', variables.ref], context?.previous);
    },
    onSettled: (data, error, variables) => {
      // Refetch after mutation
      queryClient.invalidateQueries({ queryKey: ['actions', variables.ref] });
    },
  });
}
```

**Query Key Strategy**:
- Use hierarchical keys: `['resource', params]`
- Examples:
  - `['actions']` - all actions
  - `['actions', { pack_ref: 'core' }]` - actions filtered by pack
  - `['actions', 'core.http']` - specific action
  - `['executions', { status: 'running' }]` - filtered executions

**Cache Invalidation Patterns**:
- After mutations: invalidate related queries
- WebSocket updates: update specific cache entries
- Manual refresh: invalidate and refetch
- Periodic background updates for critical data (running executions)

### Authentication

#### **JWT Token Management**
- **Storage**: Access token in memory, refresh token in httpOnly cookie (if backend supports) or localStorage
- **Flow**:
  1. User logs in → receives access token (1h) + refresh token (7d)
  2. Store tokens securely
  3. Axios interceptor adds access token to all requests
  4. On 401 response, attempt refresh
  5. If refresh succeeds, retry original request
  6. If refresh fails, redirect to login

**Auth Context**:
```typescript
// src/contexts/AuthContext.tsx
import React, { createContext, useContext, useState, useEffect } from 'react';
import { AuthService } from '@/api/services/AuthService';

interface User {
  id: number;
  username: string;
  email: string;
  roles: string[];
}

interface AuthContextType {
  user: User | null;
  isAuthenticated: boolean;
  isLoading: boolean;
  login: (username: string, password: string) => Promise<void>;
  logout: () => Promise<void>;
  refreshUser: () => Promise<void>;
}

const AuthContext = createContext<AuthContextType | undefined>(undefined);

export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<User | null>(null);
  const [isLoading, setIsLoading] = useState(true);
  
  // Load user on mount
  useEffect(() => {
    loadUser();
  }, []);
  
  const loadUser = async () => {
    try {
      const response = await AuthService.getCurrentUser();
      setUser(response.data);
    } catch (error) {
      setUser(null);
    } finally {
      setIsLoading(false);
    }
  };
  
  const login = async (username: string, password: string) => {
    const response = await AuthService.login({ requestBody: { username, password } });
    localStorage.setItem('access_token', response.data.access_token);
    localStorage.setItem('refresh_token', response.data.refresh_token);
    await loadUser();
  };
  
  const logout = async () => {
    localStorage.removeItem('access_token');
    localStorage.removeItem('refresh_token');
    setUser(null);
  };
  
  return (
    <AuthContext.Provider value={{
      user,
      isAuthenticated: !!user,
      isLoading,
      login,
      logout,
      refreshUser: loadUser,
    }}>
      {children}
    </AuthContext.Provider>
  );
}

export function useAuth() {
  const context = useContext(AuthContext);
  if (!context) throw new Error('useAuth must be used within AuthProvider');
  return context;
}
```

**Protected Routes**:
```typescript
// src/components/ProtectedRoute.tsx
import { Navigate } from 'react-router-dom';
import { useAuth } from '@/contexts/AuthContext';

export function ProtectedRoute({ children }: { children: React.ReactNode }) {
  const { isAuthenticated, isLoading } = useAuth();
  
  if (isLoading) {
    return <LoadingSpinner />;
  }
  
  if (!isAuthenticated) {
    return <Navigate to="/login" replace />;
  }
  
  return <>{children}</>;
}
```

### Real-time Updates

#### **WebSocket Client**
- **Purpose**: Receive real-time notifications from notifier service
- **Connection**: WebSocket to notifier service (separate from API)
- **Protocol**: JSON messages with event types
- **Reconnection**: Automatic reconnection with exponential backoff

**WebSocket Integration**:
```typescript
// src/websocket/client.ts
import { useEffect, useRef } from 'react';
import { useQueryClient } from '@tanstack/react-query';

interface NotificationMessage {
  type: 'execution.started' | 'execution.completed' | 'execution.failed' | 
        'inquiry.created' | 'event.created';
  data: any;
  timestamp: string;
}

export function useWebSocketNotifications() {
  const wsRef = useRef<WebSocket | null>(null);
  const queryClient = useQueryClient();
  const reconnectTimeoutRef = useRef<NodeJS.Timeout>();
  const reconnectAttempts = useRef(0);
  
  useEffect(() => {
    const connect = () => {
      const wsUrl = import.meta.env.VITE_WS_URL || 'ws://localhost:8081';
      const token = localStorage.getItem('access_token');
      
      wsRef.current = new WebSocket(`${wsUrl}?token=${token}`);
      
      wsRef.current.onopen = () => {
        console.log('WebSocket connected');
        reconnectAttempts.current = 0;
      };
      
      wsRef.current.onmessage = (event) => {
        const message: NotificationMessage = JSON.parse(event.data);
        handleNotification(message);
      };
      
      wsRef.current.onclose = () => {
        console.log('WebSocket disconnected');
        scheduleReconnect();
      };
      
      wsRef.current.onerror = (error) => {
        console.error('WebSocket error:', error);
      };
    };
    
    const scheduleReconnect = () => {
      const delay = Math.min(1000 * Math.pow(2, reconnectAttempts.current), 30000);
      reconnectAttempts.current++;
      
      reconnectTimeoutRef.current = setTimeout(() => {
        connect();
      }, delay);
    };
    
    const handleNotification = (message: NotificationMessage) => {
      switch (message.type) {
        case 'execution.started':
        case 'execution.completed':
        case 'execution.failed':
          // Update execution in cache
          queryClient.setQueryData(
            ['executions', message.data.id],
            message.data
          );
          // Invalidate execution lists
          queryClient.invalidateQueries({ queryKey: ['executions'] });
          break;
          
        case 'inquiry.created':
          // Add notification to UI
          queryClient.invalidateQueries({ queryKey: ['inquiries'] });
          break;
          
        case 'event.created':
          // Update event stream
          queryClient.invalidateQueries({ queryKey: ['events'] });
          break;
      }
    };
    
    connect();
    
    return () => {
      if (reconnectTimeoutRef.current) {
        clearTimeout(reconnectTimeoutRef.current);
      }
      if (wsRef.current) {
        wsRef.current.close();
      }
    };
  }, [queryClient]);
}
```

**Usage in App**:
```typescript
// src/App.tsx
function App() {
  useWebSocketNotifications(); // Connect once at app level
  
  return (
    <AuthProvider>
      <QueryClientProvider client={queryClient}>
        <RouterProvider router={router} />
      </QueryClientProvider>
    </AuthProvider>
  );
}
```

### Workflow Visualization

#### **React Flow**
- **Purpose**: Visual workflow editor and execution graph display
- **Why**: Best-in-class workflow visualization library for React
- **Features**:
  - Drag-and-drop node creation
  - Custom node types (action, decision, parallel, inquiry)
  - Edge routing and validation
  - Minimap and controls
  - Export to image
  - Zoom and pan

**Workflow Editor Example**:
```typescript
// src/components/WorkflowEditor.tsx
import ReactFlow, { 
  Background, 
  Controls, 
  MiniMap,
  addEdge,
  useNodesState,
  useEdgesState,
} from 'reactflow';
import 'reactflow/dist/style.css';

const nodeTypes = {
  action: ActionNode,
  decision: DecisionNode,
  parallel: ParallelNode,
  inquiry: InquiryNode,
};

export function WorkflowEditor({ workflow }: { workflow: Workflow }) {
  const [nodes, setNodes, onNodesChange] = useNodesState(
    convertWorkflowToNodes(workflow)
  );
  const [edges, setEdges, onEdgesChange] = useEdgesState(
    convertWorkflowToEdges(workflow)
  );
  
  const onConnect = useCallback(
    (params) => setEdges((eds) => addEdge(params, eds)),
    [setEdges]
  );
  
  return (
    <div style={{ height: '600px' }}>
      <ReactFlow
        nodes={nodes}
        edges={edges}
        onNodesChange={onNodesChange}
        onEdgesChange={onEdgesChange}
        onConnect={onConnect}
        nodeTypes={nodeTypes}
        fitView
      >
        <Background />
        <Controls />
        <MiniMap />
      </ReactFlow>
    </div>
  );
}
```

**Custom Node Types**:
- **ActionNode**: Represents an action execution with status indicator
- **DecisionNode**: Conditional branching with expression display
- **ParallelNode**: Concurrent execution branches
- **InquiryNode**: Human-in-the-loop interaction points

### Code/YAML Editing

#### **Monaco Editor**
- **Purpose**: Rich code/YAML editor for workflows, rules, and configurations
- **Why**: Same editor as VS Code, excellent language support
- **Features**:
  - Syntax highlighting for YAML, JSON, Python, JavaScript
  - Auto-completion
  - Error detection and linting
  - Diff editor for comparing versions
  - Themes (light/dark)

**Monaco Integration**:
```typescript
// src/components/CodeEditor.tsx
import Editor from '@monaco-editor/react';

interface CodeEditorProps {
  value: string;
  onChange: (value: string) => void;
  language: 'yaml' | 'json' | 'python' | 'javascript';
  readOnly?: boolean;
}

export function CodeEditor({ 
  value, 
  onChange, 
  language, 
  readOnly = false 
}: CodeEditorProps) {
  return (
    <Editor
      height="400px"
      language={language}
      value={value}
      onChange={(val) => onChange(val || '')}
      theme="vs-dark"
      options={{
        readOnly,
        minimap: { enabled: false },
        lineNumbers: 'on',
        scrollBeyondLastLine: false,
        automaticLayout: true,
      }}
    />
  );
}
```

**YAML Validation**:
```typescript
// src/utils/yamlValidation.ts
import yaml from 'js-yaml';
import Ajv from 'ajv';

export function validateWorkflowYAML(content: string): ValidationResult {
  try {
    const parsed = yaml.load(content);
    // Validate against workflow schema
    const ajv = new Ajv();
    const valid = ajv.validate(workflowSchema, parsed);
    
    if (!valid) {
      return { valid: false, errors: ajv.errors };
    }
    
    return { valid: true, data: parsed };
  } catch (error) {
    return { 
      valid: false, 
      errors: [{ message: error.message }] 
    };
  }
}
```

### UI Components

#### **shadcn/ui**
- **Purpose**: High-quality, accessible component library
- **Why**: Copy-paste components (no NPM bloat), fully customizable, Tailwind-based
- **Components Used**:
  - Button, Input, Select, Checkbox
  - Table, Card, Dialog, Popover
  - Tabs, Accordion, Sheet (side panel)
  - Toast (notifications)
  - Command (command palette)
  - Form (with react-hook-form integration)

**Installation**: Components are copied into project, not installed as dependency

**Usage Pattern**:
```typescript
// src/components/actions/ActionList.tsx
import { Button } from '@/components/ui/button';
import { Table } from '@/components/ui/table';
import { useActions } from '@/hooks/useActions';

export function ActionList() {
  const { data, isLoading } = useActions();
  
  if (isLoading) return <Skeleton />;
  
  return (
    <div>
      <div className="flex justify-between mb-4">
        <h1>Actions</h1>
        <Button onClick={handleCreate}>Create Action</Button>
      </div>
      
      <Table>
        {/* Table content */}
      </Table>
    </div>
  );
}
```

#### **Tailwind CSS**
- **Purpose**: Utility-first CSS framework
- **Why**: Fast styling, consistent design system, small production bundle
- **Configuration**:
  - Custom color palette matching Attune brand
  - Dark mode support
  - Responsive breakpoints
  - Custom animations for loading states

### Routing

#### **React Router v6**
- **Purpose**: Client-side routing and navigation
- **Why**: Standard React routing solution, type-safe with TypeScript
- **Features**:
  - Nested routes
  - Lazy loading for code splitting
  - Protected routes
  - URL parameter handling

**Route Structure**:
```typescript
// src/router.tsx
import { createBrowserRouter } from 'react-router-dom';

export const router = createBrowserRouter([
  {
    path: '/login',
    element: <LoginPage />,
  },
  {
    path: '/',
    element: <ProtectedRoute><Layout /></ProtectedRoute>,
    children: [
      { index: true, element: <DashboardPage /> },
      
      // Packs
      { path: 'packs', element: <PackListPage /> },
      { path: 'packs/:ref', element: <PackDetailPage /> },
      
      // Actions
      { path: 'actions', element: <ActionListPage /> },
      { path: 'actions/:ref', element: <ActionDetailPage /> },
      { path: 'actions/:ref/edit', element: <ActionEditPage /> },
      
      // Rules
      { path: 'rules', element: <RuleListPage /> },
      { path: 'rules/:ref', element: <RuleDetailPage /> },
      
      // Workflows
      { path: 'workflows', element: <WorkflowListPage /> },
      { path: 'workflows/:ref', element: <WorkflowDetailPage /> },
      { path: 'workflows/:ref/edit', element: <WorkflowEditorPage /> },
      
      // Executions
      { path: 'executions', element: <ExecutionListPage /> },
      { path: 'executions/:id', element: <ExecutionDetailPage /> },
      
      // Events & Enforcements
      { path: 'events', element: <EventListPage /> },
      { path: 'enforcements', element: <EnforcementListPage /> },
      
      // Inquiries
      { path: 'inquiries', element: <InquiryListPage /> },
      { path: 'inquiries/:id', element: <InquiryDetailPage /> },
      
      // Settings
      { path: 'settings', element: <SettingsPage /> },
      { path: 'settings/secrets', element: <SecretsPage /> },
    ],
  },
]);
```

### State Management

**Approach**: Minimal global state, prefer server state (React Query) and component state

**Global State (if needed)**:
- **Zustand**: Lightweight state management for truly global UI state
- Use cases:
  - Theme preference (light/dark)
  - Sidebar collapsed/expanded state
  - User preferences
  - Command palette open/closed

```typescript
// src/stores/uiStore.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

interface UIState {
  theme: 'light' | 'dark';
  sidebarCollapsed: boolean;
  toggleTheme: () => void;
  toggleSidebar: () => void;
}

export const useUIStore = create<UIState>()(
  persist(
    (set) => ({
      theme: 'dark',
      sidebarCollapsed: false,
      toggleTheme: () => set((state) => ({ 
        theme: state.theme === 'light' ? 'dark' : 'light' 
      })),
      toggleSidebar: () => set((state) => ({ 
        sidebarCollapsed: !state.sidebarCollapsed 
      })),
    }),
    { name: 'attune-ui-store' }
  )
);
```

## Project Structure

```
attune-web/
├── public/
│   ├── favicon.ico
│   └── logo.svg
├── src/
│   ├── api/                      # OpenAPI generated code
│   │   ├── models/              # TypeScript interfaces for all DTOs
│   │   ├── services/            # API service classes
│   │   └── core/                # Axios client configuration
│   ├── components/
│   │   ├── ui/                  # shadcn/ui base components
│   │   ├── layout/              # Layout components (Sidebar, Header, etc.)
│   │   ├── actions/             # Action-related components
│   │   ├── rules/               # Rule-related components
│   │   ├── workflows/           # Workflow editor and viewer
│   │   ├── executions/          # Execution monitoring
│   │   ├── events/              # Event stream
│   │   └── common/              # Shared components
│   ├── hooks/                   # Custom React hooks
│   │   ├── useActions.ts
│   │   ├── useRules.ts
│   │   ├── useWorkflows.ts
│   │   ├── useExecutions.ts
│   │   └── useWebSocket.ts
│   ├── contexts/                # React contexts
│   │   └── AuthContext.tsx
│   ├── stores/                  # Zustand stores (minimal)
│   │   └── uiStore.ts
│   ├── pages/                   # Page components
│   │   ├── DashboardPage.tsx
│   │   ├── LoginPage.tsx
│   │   ├── actions/
│   │   ├── rules/
│   │   ├── workflows/
│   │   └── executions/
│   ├── websocket/               # WebSocket client
│   │   └── client.ts
│   ├── utils/                   # Utility functions
│   │   ├── formatters.ts
│   │   ├── validators.ts
│   │   └── yaml.ts
│   ├── types/                   # Additional TypeScript types
│   ├── router.tsx               # React Router configuration
│   ├── App.tsx                  # Root component
│   └── main.tsx                 # Entry point
├── .env.development             # Dev environment variables
├── .env.production              # Prod environment variables
├── index.html
├── package.json
├── tsconfig.json
├── vite.config.ts
└── tailwind.config.js
```

## Development Workflow

### Initial Setup

```bash
# Create React + TypeScript project with Vite
npm create vite@latest attune-web -- --template react-ts
cd attune-web

# Install core dependencies
npm install react-router-dom @tanstack/react-query axios
npm install @monaco-editor/react reactflow zustand js-yaml

# Install UI dependencies
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

# Install dev dependencies
npm install -D @types/node openapi-typescript-codegen

# Initialize shadcn/ui
npx shadcn-ui@latest init
```

### Generate API Client

```bash
# Generate TypeScript client from OpenAPI spec
# Run this whenever the API changes
npm run generate:api
```

**package.json script**:
```json
{
  "scripts": {
    "generate:api": "openapi-typescript-codegen --input http://localhost:8080/api-spec/openapi.json --output ./src/api --client axios --useOptions"
  }
}
```

### Development Server

```bash
# Start dev server with HMR
npm run dev

# Build for production
npm run build

# Preview production build
npm run preview
```

### Environment Variables

**`.env.development`**:
```env
VITE_API_URL=http://localhost:8080
VITE_WS_URL=ws://localhost:8081
VITE_LOG_LEVEL=debug
```

**`.env.production`**:
```env
VITE_API_URL=https://api.attune.example.com
VITE_WS_URL=wss://notifications.attune.example.com
VITE_LOG_LEVEL=error
```

## Form Management and Entity Editability

### Pack-Based vs UI-Configurable Components

Attune uses a **pack-based architecture** where most automation components are defined as code and bundled into packs. The Web UI must respect these architectural constraints when providing forms.

#### Code-Based Components (NOT UI-Editable)

**Actions**:
- Implemented as executable code (Python, Node.js, Shell)
- Registered when a pack is loaded/installed
- **No create/edit forms** in Web UI
- Managed through pack lifecycle (install, update, uninstall)
- Rationale: Security, performance, code quality, testing requirements

**Sensors**:
- Implemented as executable code with event monitoring logic
- Registered when a pack is loaded/installed
- **No create/edit forms** in Web UI
- Managed through pack lifecycle
- Rationale: Require event loop integration, complex dependencies, safety

#### Mixed Model Components

**Triggers**:
- **Pack-based triggers**: Registered with system packs (e.g., `slack.message_received`)
  - **NOT UI-editable** - defined in pack manifest
- **Ad-hoc triggers**: For custom integrations without code
  - **UI-editable** via trigger form (future feature)
  - Only for ad-hoc packs (`system: false`)
  - Define parameters schema and payload schema

#### Always UI-Configurable Components

**Rules**:
- Connect triggers to actions with criteria and parameters
- No code execution, just data mapping
- **Full CRUD operations** via Web UI
- Users need flexibility to change business logic

**Packs (Ad-Hoc)**:
- User-created packs for custom automation
- **Full CRUD operations** via Web UI
- Define configuration schema (JSON Schema format)
- No code required

**Workflows** (Future):
- Multi-step automation sequences
- Visual workflow editor (React Flow)
- Workflow actions (special configurable actions)

### Form Implementation Guidelines

**Required Forms**:
1. ✅ **RuleForm** (`/rules/new`, `/rules/:id/edit`) - Implemented
2. ✅ **PackForm** (`/packs/new`, `/packs/:name/edit`) - Implemented
3. 🔄 **TriggerForm** (`/triggers/new`, `/triggers/:id/edit`) - Future
   - Only for ad-hoc packs
   - Validate pack is non-system before allowing trigger creation
4. 🔄 **WorkflowForm** (`/workflows/new`, `/workflows/:ref/edit`) - Future

**NOT Required**:
- ❌ ActionForm - Actions are code-based
- ❌ SensorForm - Sensors are code-based

### Form Validation Strategy

**Client-Side Validation**:
- Required field checks
- Format validation (names, versions, JSON syntax)
- JSON Schema validation for configuration schemas
- Real-time error display with field-level messages

**Server-Side Validation**:
- API error capture and display
- Generic error handling for network failures
- Field-specific errors when available

### Form State Management

```typescript
// Example: RuleForm component structure
function RuleForm({ rule, onSuccess, onCancel }: RuleFormProps) {
  const isEditing = !!rule;
  
  // Local form state
  const [packId, setPackId] = useState(rule?.pack_id || 0);
  const [triggerId, setTriggerId] = useState(rule?.trigger_id || 0);
  const [actionId, setActionId] = useState(rule?.action_id || 0);
  const [errors, setErrors] = useState<Record<string, string>>({});
  
  // Data fetching
  const { data: packs } = usePacks();
  const { data: triggers } = usePackTriggers(selectedPackName);
  const { data: actions } = usePackActions(selectedPackName);
  
  // Mutations
  const createRule = useCreateRule();
  const updateRule = useUpdateRule();
  
  // Validation and submission
  const validateForm = () => { /* ... */ };
  const handleSubmit = async (e) => { /* ... */ };
  
  return <form onSubmit={handleSubmit}>...</form>;
}
```

**Key Patterns**:
- Cascading dropdowns (pack → triggers/actions)
- Immutable fields when editing (pack, trigger, action IDs)
- JSON editors with syntax validation
- Optimistic UI updates after mutations
- Auto-navigation after successful creation

### Entity List Pages with Create Buttons

**Pattern**: List pages should have a prominent "Create" button when entity creation is allowed.

**Implemented**:
- ✅ Rules list page: "Create Rule" button → `/rules/new`
- ✅ Packs list page: "Register Pack" button → `/packs/new`

**Should NOT Have Create Buttons**:
- ❌ Actions list page - Actions are code-based
- ❌ Sensors list page - Sensors are code-based

**Future**:
- 🔄 Triggers list page: "Create Trigger" button (only shows for ad-hoc packs)
- 🔄 Workflows list page: "Create Workflow" button

See `docs/pack-management-architecture.md` for detailed architectural rationale.

---

## Key Features Implementation

### Dashboard

- **Real-time metrics**: Active executions, success/failure rates, event throughput
- **Recent activity**: Latest executions, events, and inquiries
- **Quick actions**: Common tasks (run workflow, create rule)
- **System health**: Service status indicators

### Pack Management

- **List view**: All packs with search and filters
- **Detail view**: Pack info, contained actions/rules/workflows
- **Create/Edit**: Form for pack metadata
- **Sync workflows**: Trigger workflow sync from pack directory

### Action Management

- **List view**: Searchable, filterable table of actions
- **Detail view**: Action parameters, metadata, associated rules
- **Create/Edit**: Form with parameter schema editor
- **Test runner**: Execute action with test parameters

### Rule Management

- **List view**: All rules with enable/disable toggle
- **Detail view**: Trigger, action, parameter mapping, criteria
- **Create/Edit**: Visual rule builder with expression editor
- **Testing**: Test rule against sample event payload

### Workflow Management

- **List view**: Workflows with status, tags, last execution
- **Visual editor**: React Flow-based workflow designer
- **YAML editor**: Monaco editor with validation
- **Dual mode**: Switch between visual and code editing
- **Execution history**: View past executions with drill-down

### Execution Monitoring

- **Live dashboard**: Real-time execution updates via WebSocket
- **Filterable list**: By status, action, pack, time range
- **Detail view**: Full execution context, logs, parent/child relationships
- **Retry/Cancel**: Actions on executions
- **Log streaming**: Real-time log output for running executions

### Event Stream

- **Live feed**: Real-time event stream
- **Filters**: By trigger, status, time range
- **Detail view**: Event payload, resulting enforcements
- **Replay**: Re-evaluate rules against past events

### Inquiry Management

- **Notification center**: Pending inquiries requiring action
- **Response interface**: Form for inquiry responses
- **History**: Past inquiries with responses

## Performance Optimizations

1. **Code Splitting**: Lazy load routes with `React.lazy()`
2. **Bundle Optimization**: Tree shaking, minification via Vite
3. **Image Optimization**: WebP format, lazy loading
4. **Query Caching**: Intelligent cache times based on data volatility
5. **Virtual Scrolling**: For large lists (executions, events)
6. **Debounced Search**: Avoid excessive API calls
7. **Optimistic Updates**: Immediate UI feedback on mutations
8. **Prefetching**: Prefetch likely next pages on hover

## Testing Strategy

### Unit Tests
- Component logic with React Testing Library
- Hook behavior with `@testing-library/react-hooks`
- Utility functions with Jest

### Integration Tests
- User flows (login, create workflow, monitor execution)
- API client mocking with MSW (Mock Service Worker)
- Form validation and submission

### E2E Tests
- Critical paths with Playwright
- Cross-browser testing
- Accessibility testing with axe-core

## Deployment

### Build Output
```bash
npm run build
# Outputs to dist/ directory
```

### Static Hosting
- Deploy `dist/` to any static host (Nginx, Cloudflare Pages, Vercel, Netlify)
- Configure routing: redirect all requests to `index.html` for SPA routing

### Docker Container
```dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
```

**nginx.conf**:
```nginx
server {
  listen 80;
  root /usr/share/nginx/html;
  index index.html;

  # SPA routing: redirect all to index.html
  location / {
    try_files $uri $uri/ /index.html;
  }

  # Cache static assets
  location ~* \.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
  }
}
```

## Security Considerations

1. **XSS Prevention**: React's automatic escaping, CSP headers
2. **Token Storage**: Access token in memory (preferred) or localStorage with caution
3. **API Communication**: HTTPS only in production
4. **Input Validation**: Client-side validation + server-side enforcement
5. **CORS**: Proper CORS configuration on API service
6. **CSP Headers**: Content Security Policy to prevent injection attacks

## Accessibility

1. **Semantic HTML**: Proper heading hierarchy, landmarks
2. **ARIA Labels**: For interactive elements and dynamic content
3. **Keyboard Navigation**: All features accessible via keyboard
4. **Focus Management**: Clear focus indicators, focus trapping in modals
5. **Screen Reader Support**: Announcements for dynamic updates
6. **Color Contrast**: WCAG AA compliance minimum

## Browser Support

- **Modern Browsers**: Chrome, Firefox, Safari, Edge (latest 2 versions)
- **No IE Support**: Modern JavaScript and CSS features used

## Future Enhancements

1. **Progressive Web App**: Offline support, install prompt
2. **Internationalization**: i18n support with react-i18next
3. **Advanced Workflow Editor**: Template library, drag-from-palette
4. **Collaboration**: Multi-user editing indicators
5. **Telemetry**: Usage analytics and error tracking
6. **Mobile Responsive**: Optimized layouts for tablets and phones
7. **Command Palette**: Keyboard-driven navigation (Cmd+K)
8. **Export/Import**: Workflow and pack export in various formats

## Documentation for Developers

All developers working on the web UI should familiarize themselves with:
1. This architecture document
2. OpenAPI specification at `/api-spec/openapi.json`
3. React Query documentation for data fetching patterns
4. shadcn/ui component documentation
5. React Flow documentation for workflow editor

## Troubleshooting Common Issues

### API Client Out of Sync
**Problem**: TypeScript errors after API changes  
**Solution**: Regenerate client with `npm run generate:api`

### WebSocket Not Connecting
**Problem**: Real-time updates not working  
**Solution**: Check VITE_WS_URL, verify notifier service is running

### Authentication Loops
**Problem**: Constant redirects to login  
**Solution**: Check token expiry, verify refresh token mechanism

### Slow Initial Load
**Problem**: Large bundle size  
**Solution**: Review code splitting, check for unnecessary dependencies in bundle

---

This architecture provides a solid foundation for building a professional, performant, and maintainable web interface for the Attune automation platform.