# UI Reflection System
## Overview
The UI Reflection System provides a live, high-level JSON description of the application's UI state, enabling automated testing, LLM-driven interactions, and real-time UI state monitoring. The system captures information about buttons, dialogs, forms, data grids, and other UI components, making the application's interface programmatically observable and controllable.
## Key Features
- **Live UI State**: Real-time JSON representation of UI components and their states
- **Stable Component IDs**: Consistent identifiers for reliable automated testing
- **Type Safety**: Full TypeScript support for component definitions
- **Automatic State Updates**: Components self-report their state changes
- **WebSocket Broadcasting**: UI state changes are broadcast for external tools
- **Minimal Boilerplate**: Easy integration with existing components
- **Hierarchical Structure**: Automatic parent-child relationships through context
- **Auto ID Generation**: Consistent ID generation with parent context integration
## Hierarchical Component Model
The UI reflection system uses React Context to maintain parent-child relationships automatically:
### 1. Base Component Structure
All components can participate in parent-child relationships:
```typescript
type ActionType = 'click' | 'type' | 'select' | 'focus' | 'open' | 'close' | 'toggle' | 'clear' | 'search' | 'navigate';
interface ActionParameter {
name: string;
type: string;
required?: boolean;
options?: string[];
description?: string;
defaultValue?: any;
}
interface ComponentAction {
type: ActionType;
available: boolean;
description: string;
parameters?: ActionParameter[];
prerequisites?: string[];
}
interface BaseComponent {
id: string; // Unique identifier (can be auto-generated)
type: string; // Component type (e.g., 'container', 'button')
label?: string; // Human-readable label
disabled?: boolean; // Component state
helperText?: string; // Helper text for the component
actions?: ComponentAction[]; // Available actions
parentId?: string; // Parent component ID
children?: UIComponent[]; // Child components
ordinal?: number; // Ordering index
}
```
### UIComponent Union Type
The `UIComponent` type is a union of all supported component types:
```typescript
type UIComponent =
| ButtonComponent
| DialogComponent
| FormComponent
| FormFieldComponent
| NavigationComponent
| DataTableComponent
| ContainerComponent
| CardComponent
| DrawerComponent
| DatePickerComponent
| TimePickerComponent
| DateTimePickerComponent
| DropdownMenuComponent
| MenuItemComponent
| InputComponent
| TextComponent;
```
### 2. Parent-Child Registration
Components inherit their parent's ID through context:
```typescript
// Parent container sets the context
{/* Children automatically know their parent */}
// Gets ticketing-dashboard as parent
// Gets ticketing-dashboard as parent
```
### 3. State Management
The UIStateContext maintains the complete component hierarchy:
```typescript
// Internal state structure
{
components: [
{
id: 'ticketing-dashboard',
type: 'container',
children: [
{
id: 'ticketing-dashboard-filters',
type: 'container'
},
{
id: 'ticketing-dashboard-table',
type: 'container',
children: [
{
id: 'ticketing-dashboard-table-status-select',
type: 'formField',
fieldType: 'select'
}
]
}
]
}
]
}
```
## Implementation Guide
### 1. Setting Up the Provider
Wrap your application or specific pages with the UIStateProvider:
```tsx
import { ClientUIStateProvider } from '@alga-psa/ui/ui-reflection/ClientUIStateProvider';
function App() {
return (
);
}
```
> **Note**: `ClientUIStateProvider` is a wrapper around `UIStateProvider` (from `packages/ui/src/ui-reflection/UIStateContext`) that handles client-side initialization. Use `ClientUIStateProvider` in application setup.
### 2. Component Registration
Use the `useAutomationIdAndRegister` hook to register components and get DOM props:
```tsx
function ActionButton({ id, label, disabled, onClick }: Props) {
// Single hook call for registration and DOM props
// Actions are passed as a separate parameter (not part of the component object)
const { automationIdProps, updateMetadata, updateActions } = useAutomationIdAndRegister(
{
id, // Optional - will auto-generate if not provided
type: 'button',
label,
disabled,
},
[{ type: 'click', available: !disabled }] // Actions as second parameter
);
// Update metadata when props change
useEffect(() => {
updateMetadata({ label, disabled });
}, [label, disabled, updateMetadata]);
// Update actions when availability changes
useEffect(() => {
updateActions([{ type: 'click', available: !disabled }]);
}, [disabled, updateActions]);
return (
);
}
```
#### Hook Signature
```typescript
type ActionConfig = ComponentAction[] | (() => ComponentAction[]);
function useAutomationIdAndRegister(
component: Omit & { id?: string },
actionsOrShouldRegister: ActionConfig | boolean = [],
overrideId?: string
): {
automationIdProps: { id: string; 'data-automation-id': string };
updateMetadata: (partial: Partial) => void;
updateActions: (newActions: ActionConfig) => void;
}
```
Key points:
- **Actions are a separate parameter**: They are not included in the component object passed as the first argument.
- **`actionsOrShouldRegister`**: Can be an array of `ComponentAction` objects, a function returning them, or a boolean to control registration.
- **`updateActions`**: Returned function to dynamically update available actions.
### 3. Form Components
Forms use ReflectionContainer to establish parent-child relationships:
```tsx
function LoginForm({ id }: Props) {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
return (
);
}
```
## Best Practices
1. **Component Registration**:
- Use ReflectionContainer for major UI sections
- Let child components inherit parent context
- Follow naming conventions for IDs:
* Screen/Page: my-screen
* Subcontainers: ${parentId}-section
* Components: ${parentId}-type
2. **State Management**:
- Use useAutomationIdAndRegister for unified registration
- Keep state updates minimal and focused
- Clean up properly in unmount handlers
- Only update properties defined in type interfaces
3. **Parent-Child Relationships**:
- Let React context handle parent-child relationships
- Keep hierarchies shallow when possible
- Clean up entire component trees on unmount
- Consider component reuse in hierarchies
4. **Type Safety**:
- Always provide specific component types to hooks
- Follow type definitions strictly
- Avoid adding custom fields not in type definitions
- Use proper type imports
5. **Performance**:
- Batch state updates when possible
- Only register components that need external visibility
- Use memoization for complex state calculations
- Consider update frequency
## Testing Integration
The UI reflection system integrates with testing through data-automation-id attributes:
```tsx
// Component registration and DOM props in one call
const { automationIdProps } = useAutomationIdAndRegister(
{
id: 'submit-button',
type: 'button',
label: 'Submit'
},
[{ type: 'click', available: true }]
);
// DOM element
```
This ensures:
- UI reflection IDs match testing selectors
- Components are consistently identifiable
- Automated tests can reliably interact with elements
- Parent-child relationships are reflected in IDs
## Contributing
To extend the system:
1. Add new component types to `packages/ui/src/ui-reflection/types.ts`
2. Update documentation
3. Add test coverage
4. Submit PR with examples
See also:
- [UI Automation IDs](ui_automation_ids.md)
- [Development Guide](../getting-started/development_guide.md)