` | Buddy agent list |
+| `mockEntryPointsResponse` | `EntryPointListResponse` | Entry points for outdial |
+| `mockAddressBookEntriesResponse` | `AddressBookEntriesResponse` | Address book contacts |
+| `makeMockAddressBook` | `Function` | Factory for custom address book mock |
+
+### Importing Fixtures
+
+```typescript
+// Import all fixtures
+import {
+ mockCC,
+ mockProfile,
+ mockTask,
+ mockQueueDetails,
+ mockAgents,
+ mockEntryPointsResponse,
+ mockAddressBookEntriesResponse,
+ makeMockAddressBook,
+} from '@webex/test-fixtures';
+
+// Use in tests
+test('example', () => {
+ expect(mockCC.stationLogin).toBeDefined();
+ expect(mockProfile.teams).toHaveLength(1);
+ expect(mockTask.data.interactionId).toBe('interaction123');
+});
+```
+
+---
+
+## Installation
+
+```bash
+# Install as dev dependency
+yarn add -D @webex/test-fixtures
+
+# Usually already included in widget package devDependencies
+```
+
+---
+
+## Additional Resources
+
+For detailed fixture structure, customization patterns, and testing strategies, see [architecture.md](./architecture.md).
+
+---
+
+_Last Updated: 2025-11-26_
+
diff --git a/packages/contact-center/test-fixtures/ai-docs/architecture.md b/packages/contact-center/test-fixtures/ai-docs/architecture.md
new file mode 100644
index 000000000..fd30c3b3b
--- /dev/null
+++ b/packages/contact-center/test-fixtures/ai-docs/architecture.md
@@ -0,0 +1,640 @@
+# Test Fixtures - Architecture
+
+## Component Overview
+
+Test Fixtures is a testing utility package that provides realistic mock data for all contact center SDK types and widgets. It follows a fixture pattern where each fixture is a pre-configured, reusable mock object that matches the actual SDK types.
+
+### Fixture Table
+
+| Fixture | Type | File | Key Properties | Customizable |
+|---------|------|------|----------------|--------------|
+| **mockCC** | `IContactCenter` | `src/fixtures.ts` | All SDK methods (stationLogin, stationLogout, setUserState, etc.) | Via jest mocking |
+| **mockProfile** | `Profile` | `src/fixtures.ts` | teams, idleCodes, wrapupCodes, agentId, loginVoiceOptions | Via object spread |
+| **mockTask** | `ITask` | `src/fixtures.ts` | data (interactionId, origin, destination), hold(), resume(), wrapup(), end() | Via jest mocking |
+| **mockQueueDetails** | `QueueDetails[]` | `src/fixtures.ts` | Queue list for transfers | Via array modification |
+| **mockAgents** | `Agent[]` | `src/fixtures.ts` | Buddy agent list | Via array modification |
+| **mockEntryPointsResponse** | `EntryPointListResponse` | `src/fixtures.ts` | Entry points for outdial | Via object spread |
+| **mockAddressBookEntriesResponse** | `AddressBookEntriesResponse` | `src/fixtures.ts` | Address book contacts | Via object spread |
+| **makeMockAddressBook** | `Function` | `src/fixtures.ts` | Factory for custom address book | Via function parameter |
+
+### File Structure
+
+```
+test-fixtures/
+├── src/
+│ ├── index.ts # Package exports
+│ └── fixtures.ts # All fixture definitions
+├── dist/
+│ ├── index.js # Build output
+│ └── types/
+│ ├── index.d.ts
+│ └── fixtures.d.ts
+├── package.json
+├── tsconfig.json
+└── webpack.config.js
+```
+
+---
+
+## Fixture Structure
+
+### mockCC (IContactCenter)
+
+Complete SDK mock with all methods as jest functions:
+
+```typescript
+const mockCC: IContactCenter = {
+ // Core methods
+ stationLogin: jest.fn(),
+ stationLogout: jest.fn(),
+ setUserState: jest.fn(),
+
+ // Task methods
+ acceptTask: jest.fn(),
+ endTask: jest.fn(),
+ holdTask: jest.fn(),
+ resumeTask: jest.fn(),
+ wrapupTask: jest.fn(),
+
+ // Transfer/Consult methods
+ consultTask: jest.fn(),
+ transferTask: jest.fn(),
+ cancelConsult: jest.fn(),
+ completeConsult: jest.fn(),
+
+ // Recording methods
+ pauseRecording: jest.fn(),
+ resumeRecording: jest.fn(),
+
+ // Outdial
+ outdial: jest.fn(),
+
+ // Proxies
+ AgentProxy: { /* agent-related methods */ },
+ DiagnosticsProxy: { /* diagnostics methods */ },
+ LoggerProxy: { /* logger methods */ },
+ ScreenRecordingProxy: { /* screen recording */ },
+ TaskProxy: { /* task subscriptions */ },
+
+ // Properties
+ version: '1.0.0',
+ initialized: true,
+};
+```
+
+**Usage:**
+
+```typescript
+// Basic usage
+test('calls stationLogin', async () => {
+ await mockCC.stationLogin({ teamId: 'team1', loginOption: 'BROWSER', dialNumber: '' });
+ expect(mockCC.stationLogin).toHaveBeenCalled();
+});
+
+// Custom mock implementation
+test('handles login error', async () => {
+ mockCC.stationLogin.mockRejectedValue(new Error('Login failed'));
+
+ await expect(mockCC.stationLogin({})).rejects.toThrow('Login failed');
+});
+```
+
+---
+
+### mockProfile (Profile)
+
+Complete agent profile with teams, idle codes, wrapup codes:
+
+```typescript
+const mockProfile: Profile = {
+ agentId: 'agent123',
+ teams: [
+ {
+ id: 'team1',
+ name: 'Team One',
+ isDefault: true,
+ // ... other team properties
+ }
+ ],
+ idleCodes: [
+ { id: 'idle1', name: 'Break', isSystem: true, isDefault: false },
+ { id: 'idle2', name: 'Lunch', isSystem: false, isDefault: true },
+ ],
+ wrapupCodes: [
+ { id: 'wrap1', name: 'Resolved', isDefault: true },
+ { id: 'wrap2', name: 'Escalated', isDefault: false },
+ ],
+ loginVoiceOptions: [
+ { label: 'Browser', value: 'BROWSER' },
+ { label: 'Extension', value: 'EXTENSION' },
+ ],
+ // ... other profile properties
+};
+```
+
+**Usage:**
+
+```typescript
+// Use as-is
+test('renders teams', () => {
+ render();
+});
+
+// Customize
+test('handles single team', () => {
+ const singleTeamProfile = {
+ ...mockProfile,
+ teams: [mockProfile.teams[0]]
+ };
+
+ render();
+});
+```
+
+---
+
+### mockTask (ITask)
+
+Active task with telephony interaction:
+
+```typescript
+const mockTask: ITask = {
+ data: {
+ interactionId: 'interaction123',
+ taskId: 'task123',
+ origin: { type: 'INBOUND', number: '+1234567890', name: 'John Doe' },
+ destination: { type: 'AGENT', number: '+0987654321' },
+ status: 'CONNECTED',
+ mediaType: 'telephony',
+ queueId: 'queue1',
+ channelType: 'telephony',
+ createdTime: Date.now(),
+ // ... other task properties
+ },
+
+ // Methods (jest mocks)
+ hold: jest.fn(),
+ resume: jest.fn(),
+ wrapup: jest.fn(),
+ end: jest.fn(),
+ transfer: jest.fn(),
+ consult: jest.fn(),
+ cancelConsult: jest.fn(),
+ completeConsult: jest.fn(),
+ pauseRecording: jest.fn(),
+ resumeRecording: jest.fn(),
+};
+```
+
+**Usage:**
+
+```typescript
+// Use task methods
+test('can hold task', async () => {
+ mockTask.hold.mockResolvedValue({ success: true });
+
+ await mockTask.hold();
+ expect(mockTask.hold).toHaveBeenCalled();
+});
+
+// Customize task data
+test('handles inbound call', () => {
+ const inboundTask = {
+ ...mockTask,
+ data: {
+ ...mockTask.data,
+ origin: { type: 'INBOUND', number: '+1111111111', name: 'Jane Smith' }
+ }
+ };
+
+ render();
+});
+```
+
+---
+
+### mockQueueDetails (QueueDetails[])
+
+List of queue configurations:
+
+```typescript
+const mockQueueDetails: QueueDetails[] = [
+ {
+ id: 'queue1',
+ name: 'Queue1',
+ statistics: {
+ agentsAvailable: 5,
+ tasksWaiting: 2,
+ // ... other stats
+ },
+ // ... other queue properties
+ },
+ {
+ id: 'queue2',
+ name: 'Queue2',
+ statistics: { /* ... */ },
+ },
+];
+```
+
+---
+
+### mockAgents (Agent[])
+
+List of buddy agents:
+
+```typescript
+const mockAgents: Agent[] = [
+ {
+ id: 'agent1',
+ name: 'Agent One',
+ state: 'Available',
+ skills: ['Support', 'Sales'],
+ // ... other agent properties
+ },
+ {
+ id: 'agent2',
+ name: 'Agent Two',
+ state: 'Idle',
+ skills: ['Technical'],
+ },
+];
+```
+
+---
+
+### makeMockAddressBook (Factory Function)
+
+Factory function to create custom address book mocks:
+
+```typescript
+const makeMockAddressBook = (
+ mockGetEntries: jest.Mock = jest.fn()
+) => ({
+ getEntries: mockGetEntries,
+ // ... other address book methods
+});
+```
+
+**Usage:**
+
+```typescript
+test('searches address book', async () => {
+ const mockGetEntries = jest.fn().mockResolvedValue({
+ data: [
+ { id: 'c1', name: 'Contact1', number: '123' },
+ ],
+ meta: { page: 0, pageSize: 25, totalPages: 1 }
+ });
+
+ const addressBook = makeMockAddressBook(mockGetEntries);
+
+ const result = await addressBook.getEntries({ search: 'Contact1' });
+
+ expect(mockGetEntries).toHaveBeenCalledWith({ search: 'Contact1' });
+ expect(result.data).toHaveLength(1);
+});
+```
+
+---
+
+## Testing Patterns
+
+### Unit Testing Widgets
+
+```mermaid
+graph TB
+ subgraph "Test Setup"
+ Fixtures[Import Fixtures]
+ Mock[Mock Store/SDK]
+ end
+
+ subgraph "Test Execution"
+ Render[Render Component]
+ Interact[User Interactions]
+ Assert[Assertions]
+ end
+
+ subgraph "Test Fixtures"
+ MockCC[mockCC]
+ MockProfile[mockProfile]
+ MockTask[mockTask]
+ end
+
+ Fixtures --> MockCC
+ Fixtures --> MockProfile
+ Fixtures --> MockTask
+
+ MockCC --> Mock
+ MockProfile --> Mock
+ MockTask --> Mock
+
+ Mock --> Render
+ Render --> Interact
+ Interact --> Assert
+
+ style Fixtures fill:#e1f5ff
+ style Mock fill:#ffe1e1
+ style Assert fill:#e1ffe1
+```
+
+### Store Mocking Pattern
+
+```typescript
+// Mock store with fixtures
+jest.mock('@webex/cc-store', () => {
+ const { mockCC, mockProfile } = require('@webex/test-fixtures');
+
+ return {
+ __esModule: true,
+ default: {
+ cc: mockCC,
+ teams: mockProfile.teams,
+ idleCodes: mockProfile.idleCodes,
+ logger: mockCC.LoggerProxy,
+ isAgentLoggedIn: false,
+ // Mock methods
+ setTeams: jest.fn(),
+ setIdleCodes: jest.fn(),
+ setIsAgentLoggedIn: jest.fn(),
+ }
+ };
+});
+```
+
+### Customization Pattern
+
+```typescript
+// Base fixture
+import { mockTask } from '@webex/test-fixtures';
+
+// Customize for specific test
+const consultingTask = {
+ ...mockTask,
+ data: {
+ ...mockTask.data,
+ status: 'CONSULTING',
+ consultedAgentId: 'agent2'
+ }
+};
+
+// Use customized fixture
+test('handles consulting state', () => {
+ render();
+ expect(screen.getByText('Consulting...')).toBeInTheDocument();
+});
+```
+
+---
+
+## Fixture Coverage
+
+### SDK Coverage
+
+| SDK Feature | Mock Provided | Customizable |
+|-------------|---------------|--------------|
+| Station Login/Logout | ✅ `mockCC.stationLogin`, `mockCC.stationLogout` | ✅ Via jest mocking |
+| User State | ✅ `mockCC.setUserState` | ✅ Via jest mocking |
+| Task Accept/End | ✅ `mockCC.acceptTask`, `mockCC.endTask` | ✅ Via jest mocking |
+| Task Hold/Resume | ✅ `mockTask.hold`, `mockTask.resume` | ✅ Via jest mocking |
+| Transfer/Consult | ✅ `mockCC.transferTask`, `mockCC.consultTask` | ✅ Via jest mocking |
+| Recording | ✅ `mockCC.pauseRecording`, `mockCC.resumeRecording` | ✅ Via jest mocking |
+| Outdial | ✅ `mockCC.outdial`, `mockEntryPointsResponse` | ✅ Via jest mocking |
+| Address Book | ✅ `makeMockAddressBook` | ✅ Via factory parameter |
+| Agent Profile | ✅ `mockProfile` | ✅ Via object spread |
+| Queues | ✅ `mockQueueDetails` | ✅ Via array modification |
+| Agents | ✅ `mockAgents` | ✅ Via array modification |
+
+---
+
+## Troubleshooting Guide
+
+### Common Issues
+
+#### 1. Type Errors with Fixtures
+
+**Symptoms:**
+- TypeScript errors when using fixtures
+- Type mismatch with actual SDK types
+
+**Possible Causes:**
+- SDK types updated but fixtures not
+- Missing required properties
+
+**Solutions:**
+
+```typescript
+// Verify fixture type matches SDK type
+import type { IContactCenter, Profile } from '@webex/contact-center';
+import { mockCC, mockProfile } from '@webex/test-fixtures';
+
+// Type assertion if needed
+const cc: IContactCenter = mockCC as IContactCenter;
+const profile: Profile = mockProfile as Profile;
+
+// Add missing properties
+const extendedProfile = {
+ ...mockProfile,
+ newProperty: 'value' // Add new required property
+};
+```
+
+#### 2. Jest Mock Not Working
+
+**Symptoms:**
+- Mock functions not being called
+- Assertions failing
+
+**Possible Causes:**
+- Mock not reset between tests
+- Wrong jest mock method
+
+**Solutions:**
+
+```typescript
+// Reset mocks in beforeEach
+beforeEach(() => {
+ jest.clearAllMocks();
+ // or
+ mockCC.stationLogin.mockClear();
+});
+
+// Use correct jest mock methods
+mockCC.stationLogin.mockResolvedValue({ success: true }); // For promises
+mockCC.stationLogin.mockReturnValue({ success: true }); // For sync
+mockCC.stationLogin.mockImplementation(async () => ({ success: true }));
+```
+
+#### 3. Store Mock Not Working in Tests
+
+**Symptoms:**
+- Widget uses actual store instead of mock
+- Mock store data not used
+
+**Possible Causes:**
+- Mock not hoisted before imports
+- Store imported before mock
+
+**Solutions:**
+
+```typescript
+// Place mock BEFORE imports
+jest.mock('@webex/cc-store', () => {
+ const { mockCC, mockProfile } = require('@webex/test-fixtures');
+ return { __esModule: true, default: { cc: mockCC, /* ... */ } };
+});
+
+// Now import widget
+import { StationLogin } from '@webex/cc-station-login';
+
+// Or use jest.doMock for dynamic mocking
+jest.doMock('@webex/cc-store', () => ({ /* mock */ }));
+```
+
+#### 4. Fixture Data Not Realistic
+
+**Symptoms:**
+- Tests pass but widget fails in production
+- Edge cases not covered
+
+**Possible Causes:**
+- Fixture data too simplified
+- Missing edge case scenarios
+
+**Solutions:**
+
+```typescript
+// Create realistic fixtures
+const realisticTask = {
+ ...mockTask,
+ data: {
+ ...mockTask.data,
+ // Add realistic data
+ createdTime: Date.now() - 60000, // 1 minute ago
+ queueTime: 30000, // 30 seconds in queue
+ origin: {
+ type: 'INBOUND',
+ number: '+12025551234', // Real format
+ name: 'John Smith'
+ },
+ }
+};
+
+// Create edge case fixtures
+const longWaitTask = {
+ ...mockTask,
+ data: {
+ ...mockTask.data,
+ queueTime: 600000, // 10 minutes (edge case)
+ }
+};
+```
+
+#### 5. Fixture Mutations Affect Other Tests
+
+**Symptoms:**
+- Tests pass in isolation but fail together
+- Flaky tests
+
+**Possible Causes:**
+- Fixture objects mutated during tests
+- Shared fixture reference
+
+**Solutions:**
+
+```typescript
+// Create fresh copy for each test
+import { mockTask } from '@webex/test-fixtures';
+
+beforeEach(() => {
+ // Deep clone fixture
+ const freshTask = JSON.parse(JSON.stringify(mockTask));
+
+ // Or use object spread (shallow)
+ const freshTask = { ...mockTask, data: { ...mockTask.data } };
+});
+
+// Or create fixture factory
+const createMockTask = () => ({
+ data: { /* ... */ },
+ hold: jest.fn(),
+ // ... other properties
+});
+
+test('test 1', () => {
+ const task = createMockTask(); // Fresh instance
+});
+```
+
+---
+
+## Best Practices
+
+### 1. Reset Mocks Between Tests
+
+```typescript
+beforeEach(() => {
+ jest.clearAllMocks();
+});
+```
+
+### 2. Use Factory Functions for Complex Scenarios
+
+```typescript
+const createCustomTask = (overrides = {}) => ({
+ ...mockTask,
+ data: { ...mockTask.data, ...overrides }
+});
+
+test('handles escalated task', () => {
+ const task = createCustomTask({ escalated: true });
+ // ... test logic
+});
+```
+
+### 3. Create Reusable Test Utilities
+
+```typescript
+// test-utils.ts
+export const setupMockStore = (overrides = {}) => {
+ const mockStore = {
+ cc: mockCC,
+ teams: mockProfile.teams,
+ ...overrides
+ };
+
+ jest.mock('@webex/cc-store', () => ({ default: mockStore }));
+
+ return mockStore;
+};
+```
+
+### 4. Document Custom Fixtures
+
+```typescript
+/**
+ * Mock task in consulting state with second agent
+ * Use this for testing consult/transfer scenarios
+ */
+const consultingTask = {
+ ...mockTask,
+ data: {
+ ...mockTask.data,
+ status: 'CONSULTING',
+ consultedAgentId: 'agent2'
+ }
+};
+```
+
+---
+
+## Related Documentation
+
+- [Agent Documentation](./agent.md) - Usage examples and fixtures
+- [Testing Patterns](../../../../ai-docs/patterns/testing-patterns.md) - Testing strategies
+- [CC Store Documentation](../../store/ai-docs/agent.md) - Store mocking patterns
+
+---
+
+_Last Updated: 2025-11-26_
+
diff --git a/packages/contact-center/ui-logging/ai-docs/agent.md b/packages/contact-center/ui-logging/ai-docs/agent.md
new file mode 100644
index 000000000..9380913a1
--- /dev/null
+++ b/packages/contact-center/ui-logging/ai-docs/agent.md
@@ -0,0 +1,403 @@
+# UI Logging - Metrics Tracking Utility
+
+## Overview
+
+UI Logging is a lightweight utility package that provides metrics tracking capabilities for contact center widgets. It includes a Higher-Order Component (HOC) called `withMetrics` that automatically tracks widget lifecycle events, and a `logMetrics` function for custom event logging.
+
+**Package:** `@webex/cc-ui-logging`
+
+**Version:** See [package.json](../package.json)
+
+---
+
+## Why and What is This Package Used For?
+
+### Purpose
+
+The UI Logging package enables observability and monitoring for contact center widgets. It:
+- **Tracks widget lifecycle** - Automatically logs mount, unmount, and updates
+- **Provides HOC wrapper** - Easy integration with minimal code changes
+- **Logs to store logger** - Integrates with existing logging infrastructure
+- **Supports custom metrics** - Log custom events with additional context
+- **Optimizes re-renders** - Includes shallow props comparison for performance
+
+### Key Capabilities
+
+- **withMetrics HOC**: Wraps components to auto-track lifecycle events
+- **logMetrics Function**: Manually log custom events
+- **havePropsChanged Utility**: Shallow comparison to prevent unnecessary re-renders
+- **Type-Safe**: Full TypeScript support with WidgetMetrics type
+- **Store Integration**: Uses store.logger for centralized logging
+
+---
+
+## Examples and Use Cases
+
+### Getting Started
+
+#### Basic HOC Usage
+
+```typescript
+import { withMetrics } from '@webex/cc-ui-logging';
+import MyWidget from './MyWidget';
+
+// Wrap your widget with metrics tracking
+const MyWidgetWithMetrics = withMetrics(MyWidget, 'MyWidget');
+
+// Use the wrapped component
+function App() {
+ return ;
+}
+
+// Automatically logs:
+// - WIDGET_MOUNTED when component mounts
+// - WIDGET_UNMOUNTED when component unmounts
+```
+
+#### Manual Metrics Logging
+
+```typescript
+import { logMetrics } from '@webex/cc-ui-logging';
+
+function MyComponent() {
+ const handleButtonClick = () => {
+ // Log custom event
+ logMetrics({
+ widgetName: 'MyComponent',
+ event: 'ERROR',
+ timestamp: Date.now(),
+ additionalContext: {
+ errorCode: 'LOGIN_FAILED',
+ reason: 'Invalid credentials'
+ }
+ });
+ };
+
+ return ;
+}
+```
+
+### Common Use Cases
+
+#### 1. Tracking Widget Lifecycle
+
+```typescript
+import { withMetrics } from '@webex/cc-ui-logging';
+import { StationLogin } from './StationLogin';
+
+// Automatically tracks mount/unmount
+const StationLoginWithMetrics = withMetrics(
+ StationLogin,
+ 'StationLogin'
+);
+
+// When used in app:
+
+
+// Logs on mount:
+// {
+// widgetName: 'StationLogin',
+// event: 'WIDGET_MOUNTED',
+// timestamp: 1700000000000
+// }
+
+// Logs on unmount:
+// {
+// widgetName: 'StationLogin',
+// event: 'WIDGET_UNMOUNTED',
+// timestamp: 1700000100000
+// }
+```
+
+#### 2. Logging Errors
+
+```typescript
+import { logMetrics } from '@webex/cc-ui-logging';
+
+function UserState() {
+ const handleStateChange = async (newState) => {
+ try {
+ await updateState(newState);
+ } catch (error) {
+ // Log error with context
+ logMetrics({
+ widgetName: 'UserState',
+ event: 'ERROR',
+ timestamp: Date.now(),
+ props: { attemptedState: newState },
+ additionalContext: {
+ error: error.message,
+ stack: error.stack
+ }
+ });
+ }
+ };
+
+ return ;
+}
+```
+
+#### 3. Performance Tracking
+
+```typescript
+import { logMetrics } from '@webex/cc-ui-logging';
+import { useEffect } from 'react';
+
+function TaskList({ tasks }) {
+ useEffect(() => {
+ const startTime = performance.now();
+
+ // Render tasks
+ renderTasks(tasks);
+
+ const endTime = performance.now();
+
+ // Log render performance
+ logMetrics({
+ widgetName: 'TaskList',
+ event: 'WIDGET_MOUNTED',
+ timestamp: Date.now(),
+ additionalContext: {
+ renderTime: endTime - startTime,
+ taskCount: tasks.length
+ }
+ });
+ }, [tasks]);
+
+ return {/* task list */}
;
+}
+```
+
+#### 4. User Interaction Tracking
+
+```typescript
+import { logMetrics } from '@webex/cc-ui-logging';
+
+function CallControl({ task }) {
+ const handleHold = () => {
+ logMetrics({
+ widgetName: 'CallControl',
+ event: 'WIDGET_MOUNTED', // Using WIDGET_MOUNTED for custom events
+ timestamp: Date.now(),
+ props: { taskId: task.id },
+ additionalContext: {
+ action: 'hold_clicked',
+ callDuration: task.duration
+ }
+ });
+
+ // Perform hold action
+ task.hold();
+ };
+
+ return ;
+}
+```
+
+### Integration Patterns
+
+#### With Widget Components
+
+```typescript
+import { withMetrics } from '@webex/cc-ui-logging';
+import { observer } from 'mobx-react-lite';
+import { UserStateComponent } from '@webex/cc-components';
+import store from '@webex/cc-store';
+
+// 1. Create internal component
+const UserStateInternal = observer(({ onStateChange }) => {
+ const props = {
+ idleCodes: store.idleCodes,
+ currentState: store.currentState,
+ setAgentStatus: (code) => store.setCurrentState(code),
+ onStateChange,
+ };
+
+ return ;
+});
+
+// 2. Wrap with metrics HOC
+const UserState = withMetrics(UserStateInternal, 'UserState');
+
+export { UserState };
+```
+
+#### With Error Boundaries
+
+```typescript
+import { logMetrics } from '@webex/cc-ui-logging';
+import { ErrorBoundary } from 'react-error-boundary';
+
+function Widget(props) {
+ const handleError = (error: Error) => {
+ // Log error via metrics
+ logMetrics({
+ widgetName: 'MyWidget',
+ event: 'ERROR',
+ timestamp: Date.now(),
+ additionalContext: {
+ error: error.message,
+ componentStack: error.stack
+ }
+ });
+ };
+
+ return (
+
+
+
+ );
+}
+```
+
+#### Custom Metrics in Hooks
+
+```typescript
+import { logMetrics } from '@webex/cc-ui-logging';
+import { useEffect } from 'react';
+
+function useCustomHook(widgetName: string) {
+ useEffect(() => {
+ // Log when hook initializes
+ logMetrics({
+ widgetName,
+ event: 'WIDGET_MOUNTED',
+ timestamp: Date.now(),
+ additionalContext: {
+ hookInitialized: true
+ }
+ });
+
+ return () => {
+ // Log when hook cleans up
+ logMetrics({
+ widgetName,
+ event: 'WIDGET_UNMOUNTED',
+ timestamp: Date.now()
+ });
+ };
+ }, [widgetName]);
+}
+```
+
+---
+
+## Dependencies
+
+**Note:** For exact versions, see [package.json](../package.json)
+
+### Runtime Dependencies
+
+| Package | Purpose |
+|---------|---------|
+| `@webex/cc-store` | Access to store.logger for logging |
+
+### Peer Dependencies
+
+| Package | Purpose |
+|---------|---------|
+| `react` | React framework (for HOC) |
+| `react-dom` | React DOM (for HOC) |
+
+### Development Dependencies
+
+Key development tools (see [package.json](../package.json) for versions):
+- TypeScript
+- Jest (testing)
+- Webpack (bundling)
+
+---
+
+## API Reference
+
+### withMetrics HOC
+
+```typescript
+function withMetrics(
+ Component: React.ComponentType
,
+ widgetName: string
+): React.MemoExoticComponent>
+```
+
+**Parameters:**
+- `Component` - React component to wrap
+- `widgetName` - Name for metric identification
+
+**Returns:** Memoized component with automatic metrics tracking
+
+**Behavior:**
+- Wraps component with React.memo
+- Uses custom comparison function (`havePropsChanged`)
+- Logs WIDGET_MOUNTED on mount
+- Logs WIDGET_UNMOUNTED on unmount
+
+---
+
+### logMetrics Function
+
+```typescript
+function logMetrics(metric: WidgetMetrics): void
+
+type WidgetMetrics = {
+ widgetName: string;
+ event: 'WIDGET_MOUNTED' | 'ERROR' | 'WIDGET_UNMOUNTED' | 'PROPS_UPDATED';
+ props?: Record;
+ timestamp: number;
+ additionalContext?: Record;
+};
+```
+
+**Parameters:**
+- `metric.widgetName` - Widget identifier
+- `metric.event` - Event type
+- `metric.props` - Optional widget props snapshot
+- `metric.timestamp` - Unix timestamp
+- `metric.additionalContext` - Optional additional data
+
+**Behavior:**
+- Checks if `store.logger` exists
+- Logs warning if no logger available
+- Calls `store.logger.log()` with formatted JSON
+
+---
+
+### havePropsChanged Function
+
+```typescript
+function havePropsChanged(prev: any, next: any): boolean
+```
+
+**Parameters:**
+- `prev` - Previous props object
+- `next` - Next props object
+
+**Returns:** `true` if props have changed, `false` otherwise
+
+**Behavior:**
+- Performs shallow comparison
+- Compares object keys length
+- Compares primitive values
+- Does NOT deep compare nested objects
+- Used by React.memo to prevent re-renders
+
+---
+
+## Installation
+
+```bash
+# Install as development or runtime dependency
+yarn add @webex/cc-ui-logging
+
+# Used internally by widgets, usually not directly installed
+```
+
+---
+
+## Additional Resources
+
+For detailed HOC implementation, metrics flow, and performance optimization, see [architecture.md](./architecture.md).
+
+---
+
+_Last Updated: 2025-11-26_
+
diff --git a/packages/contact-center/ui-logging/ai-docs/architecture.md b/packages/contact-center/ui-logging/ai-docs/architecture.md
new file mode 100644
index 000000000..9a33299d7
--- /dev/null
+++ b/packages/contact-center/ui-logging/ai-docs/architecture.md
@@ -0,0 +1,497 @@
+# UI Logging - Architecture
+
+## Component Overview
+
+UI Logging is a utility package that provides metrics tracking through a Higher-Order Component (HOC) pattern and direct logging functions. It integrates with the store's logger to provide centralized metrics collection.
+
+### Module Table
+
+| Module | File | Exports | Purpose | Dependencies |
+|--------|------|---------|---------|--------------|
+| **withMetrics HOC** | `src/withMetrics.tsx` | `withMetrics` (default) | Wraps components with lifecycle tracking | React, metricsLogger |
+| **metricsLogger** | `src/metricsLogger.ts` | `logMetrics`, `havePropsChanged`, `WidgetMetrics` (type) | Logging functions and utilities | @webex/cc-store |
+| **Package Entry** | `src/index.ts` | All exports | Main package export | Both modules above |
+
+### File Structure
+
+```
+ui-logging/
+├── src/
+│ ├── index.ts # Package exports
+│ ├── metricsLogger.ts # Logging functions
+│ └── withMetrics.tsx # HOC implementation
+├── tests/
+│ ├── metricsLogger.test.ts # Logger tests
+│ └── withMetrics.test.tsx # HOC tests
+├── dist/
+│ ├── index.js # Build output
+│ └── types/
+│ ├── index.d.ts
+│ ├── metricsLogger.d.ts
+│ └── withMetrics.d.ts
+├── package.json
+├── tsconfig.json
+└── webpack.config.js
+```
+
+---
+
+## Data Flows
+
+### Metrics Logging Flow
+
+```mermaid
+graph LR
+ subgraph "Widget/Component"
+ Component[React Component]
+ Event[User Event/Lifecycle]
+ end
+
+ subgraph "UI Logging"
+ HOC[withMetrics HOC]
+ LogFn[logMetrics Function]
+ end
+
+ subgraph "Store"
+ Logger[store.logger]
+ end
+
+ subgraph "Backend/Console"
+ Output[Log Output]
+ end
+
+ Component -->|Wrapped by| HOC
+ HOC -->|Mount/Unmount| LogFn
+ Event -->|Custom logging| LogFn
+ LogFn -->|JSON metrics| Logger
+ Logger -->|Formatted logs| Output
+
+ style HOC fill:#e1f5ff
+ style LogFn fill:#ffe1e1
+ style Logger fill:#fff4e1
+```
+
+### HOC Lifecycle Flow
+
+```mermaid
+sequenceDiagram
+ participant App as Application
+ participant HOC as withMetrics HOC
+ participant Component as Wrapped Component
+ participant Logger as logMetrics
+ participant Store as store.logger
+
+ App->>HOC: Render withMetrics(Component)
+ activate HOC
+
+ HOC->>HOC: useEffect (mount)
+ HOC->>Logger: logMetrics({event: 'WIDGET_MOUNTED'})
+ activate Logger
+ Logger->>Store: Check store.logger exists
+ alt Logger exists
+ Logger->>Store: logger.log(metrics)
+ Store-->>Logger: Logged
+ else No logger
+ Logger->>Logger: console.warn('No logger found')
+ end
+ deactivate Logger
+
+ HOC->>Component: Render with props
+ activate Component
+ Component-->>HOC: Rendered
+ deactivate Component
+
+ HOC-->>App: Rendered widget
+ deactivate HOC
+
+ Note over App,Store: Component unmounts
+
+ App->>HOC: Unmount
+ activate HOC
+ HOC->>HOC: useEffect cleanup
+ HOC->>Logger: logMetrics({event: 'WIDGET_UNMOUNTED'})
+ activate Logger
+ Logger->>Store: logger.log(metrics)
+ Store-->>Logger: Logged
+ deactivate Logger
+ deactivate HOC
+```
+
+---
+
+## Implementation Details
+
+### withMetrics HOC
+
+**File:** `src/withMetrics.tsx`
+
+The HOC wraps components to track lifecycle events:
+
+```typescript
+export default function withMetrics(
+ Component: any,
+ widgetName: string
+) {
+ return React.memo(
+ (props: P) => {
+ // Track mount and unmount
+ useEffect(() => {
+ logMetrics({
+ widgetName,
+ event: 'WIDGET_MOUNTED',
+ timestamp: Date.now(),
+ });
+
+ return () => {
+ logMetrics({
+ widgetName,
+ event: 'WIDGET_UNMOUNTED',
+ timestamp: Date.now(),
+ });
+ };
+ }, []);
+
+ return ;
+ },
+ // Custom comparison function
+ (prevProps, nextProps) => !havePropsChanged(prevProps, nextProps)
+ );
+}
+```
+
+**Key Features:**
+- Uses `React.memo` for performance optimization
+- Custom props comparison via `havePropsChanged`
+- Single `useEffect` with cleanup for lifecycle tracking
+- Props passed through transparently
+
+---
+
+### logMetrics Function
+
+**File:** `src/metricsLogger.ts`
+
+Logs metrics to store.logger:
+
+```typescript
+export const logMetrics = (metric: WidgetMetrics) => {
+ if (!store.logger) {
+ console.warn('CC-Widgets: UI Metrics: No logger found');
+ return;
+ }
+ store.logger.log(
+ `CC-Widgets: UI Metrics: ${JSON.stringify(metric, null, 2)}`,
+ {
+ module: 'metricsLogger.tsx',
+ method: 'logMetrics',
+ }
+ );
+};
+```
+
+**Behavior:**
+- Checks for `store.logger` existence
+- Warns to console if logger missing (doesn't throw)
+- Formats metrics as JSON string
+- Includes module/method context
+
+---
+
+### havePropsChanged Function
+
+**File:** `src/metricsLogger.ts`
+
+Performs shallow comparison to detect prop changes:
+
+```typescript
+export function havePropsChanged(prev: any, next: any): boolean {
+ if (prev === next) return false;
+
+ // Type check
+ if (typeof prev !== typeof next) return true;
+ if (!prev || !next) return prev !== next;
+
+ // Compare keys
+ const prevKeys = Object.keys(prev);
+ const nextKeys = Object.keys(next);
+ if (prevKeys.length !== nextKeys.length) return true;
+
+ // Compare primitive values (shallow)
+ for (const key of prevKeys) {
+ const prevVal = prev[key];
+ const nextVal = next[key];
+
+ if (prevVal === nextVal) continue;
+ if (typeof prevVal !== 'object' || prevVal === null) return true;
+ if (typeof nextVal !== 'object' || nextVal === null) return true;
+ }
+
+ return false;
+}
+```
+
+**Logic:**
+- Reference equality check first (fastest)
+- Type comparison
+- Key count comparison
+- Shallow primitive comparison
+- **Does NOT** deep compare nested objects (intentional for performance)
+
+**Use Case:**
+Used by `React.memo` to prevent unnecessary re-renders when props haven't actually changed.
+
+---
+
+## Metrics Events
+
+### Event Types
+
+| Event | When Fired | Use Case |
+|-------|-----------|----------|
+| `WIDGET_MOUNTED` | Component mounted to DOM | Track widget usage, initialization time |
+| `WIDGET_UNMOUNTED` | Component unmounted from DOM | Track session duration, cleanup |
+| `ERROR` | Error occurred | Track failures, debug issues |
+| `PROPS_UPDATED` | Props changed (future) | Track configuration changes |
+
+### Metrics Data Structure
+
+```typescript
+type WidgetMetrics = {
+ widgetName: string; // e.g., 'StationLogin'
+ event: string; // e.g., 'WIDGET_MOUNTED'
+ props?: Record; // Optional props snapshot
+ timestamp: number; // Unix timestamp
+ additionalContext?: Record; // Custom data
+};
+```
+
+**Example Logged Metric:**
+
+```json
+{
+ "widgetName": "StationLogin",
+ "event": "WIDGET_MOUNTED",
+ "timestamp": 1700000000000,
+ "props": {
+ "profileMode": false,
+ "teamId": "team123"
+ },
+ "additionalContext": {
+ "userAgent": "Chrome/120.0",
+ "sessionId": "session-abc"
+ }
+}
+```
+
+---
+
+## Performance Optimization
+
+### React.memo with Custom Comparison
+
+The HOC uses `React.memo` with `havePropsChanged` to optimize re-renders:
+
+```mermaid
+graph TD
+ Start[Props Update]
+ Compare{havePropsChanged?}
+ Rerender[Re-render Component]
+ Skip[Skip Re-render]
+
+ Start --> Compare
+ Compare -->|true| Rerender
+ Compare -->|false| Skip
+
+ style Compare fill:#ffe1e1
+ style Skip fill:#e1ffe1
+```
+
+**Benefits:**
+- Prevents unnecessary re-renders
+- Reduces PROPS_UPDATED events
+- Improves performance for widgets with frequent parent updates
+
+**Trade-off:**
+- Shallow comparison only (nested object changes might be missed)
+- Intentional design choice to avoid deep comparison overhead
+
+---
+
+## Store Integration
+
+### Logger Dependency
+
+The package relies on `store.logger` being initialized:
+
+```typescript
+// store.logger must be set before using ui-logging
+import store from '@webex/cc-store';
+
+store.setLogger({
+ log: (...args) => console.log(...args),
+ error: (...args) => console.error(...args),
+ warn: (...args) => console.warn(...args),
+ info: (...args) => console.info(...args),
+});
+
+// Now logMetrics will work
+logMetrics({ ... });
+```
+
+**Graceful Degradation:**
+- If `store.logger` is undefined, logs warning to console
+- Does NOT throw error (allows widgets to work without logger)
+
+---
+
+## Troubleshooting Guide
+
+### Common Issues
+
+#### 1. Metrics Not Logging
+
+**Symptoms:**
+- No metrics appearing in logs
+- Silent failures
+
+**Possible Causes:**
+- `store.logger` not initialized
+- Logger object missing methods
+
+**Solutions:**
+
+```typescript
+// Check if logger exists
+import store from '@webex/cc-store';
+console.log('Logger exists:', store.logger !== undefined);
+
+// Set logger if missing
+store.setLogger({
+ log: console.log,
+ error: console.error,
+ warn: console.warn,
+ info: console.info,
+});
+
+// Verify logging works
+import { logMetrics } from '@webex/cc-ui-logging';
+logMetrics({
+ widgetName: 'Test',
+ event: 'WIDGET_MOUNTED',
+ timestamp: Date.now()
+});
+```
+
+#### 2. Component Re-rendering Too Often
+
+**Symptoms:**
+- Component re-renders on every parent update
+- Performance degradation
+
+**Possible Causes:**
+- Props comparison not working
+- Passing new object/function references
+
+**Solutions:**
+
+```typescript
+// Ensure stable prop references
+import { useCallback, useMemo } from 'react';
+
+const Parent = () => {
+ // ✅ Memoized callback
+ const handleChange = useCallback(() => {}, []);
+
+ // ✅ Memoized object
+ const config = useMemo(() => ({ option: 'value' }), []);
+
+ return ;
+};
+
+// ❌ Avoid inline functions/objects
+ {}} // New function every render
+ config={{ option: 'value' }} // New object every render
+/>
+```
+
+#### 3. TypeScript Type Errors
+
+**Symptoms:**
+- Type errors with WidgetMetrics
+- Event type not recognized
+
+**Possible Causes:**
+- Using incorrect event type
+- Missing type import
+
+**Solutions:**
+
+```typescript
+// Import type
+import type { WidgetMetrics } from '@webex/cc-ui-logging';
+
+// Use correct event types
+const metric: WidgetMetrics = {
+ widgetName: 'MyWidget',
+ event: 'WIDGET_MOUNTED', // Must be one of the allowed event types
+ timestamp: Date.now()
+};
+
+// For custom events, use WIDGET_MOUNTED with additionalContext
+const customMetric: WidgetMetrics = {
+ widgetName: 'MyWidget',
+ event: 'WIDGET_MOUNTED',
+ timestamp: Date.now(),
+ additionalContext: {
+ customEvent: 'button_clicked'
+ }
+};
+```
+
+#### 4. HOC Not Tracking Unmount
+
+**Symptoms:**
+- WIDGET_MOUNTED logged
+- WIDGET_UNMOUNTED never logged
+
+**Possible Causes:**
+- Component never unmounted
+- Cleanup function not running
+- Page refreshed before unmount
+
+**Solutions:**
+
+```typescript
+// Verify component actually unmounts
+useEffect(() => {
+ console.log('Component mounted');
+
+ return () => {
+ console.log('Component cleanup'); // Should see this
+ };
+}, []);
+
+// For navigation/page changes
+window.addEventListener('beforeunload', () => {
+ // Log before page unload
+ logMetrics({
+ widgetName: 'MyWidget',
+ event: 'WIDGET_UNMOUNTED',
+ timestamp: Date.now()
+ });
+});
+```
+
+---
+
+## Related Documentation
+
+- [Agent Documentation](./agent.md) - Usage examples and API
+- [React Patterns](../../../../ai-docs/patterns/react-patterns.md) - HOC patterns
+- [CC Store Documentation](../../store/ai-docs/agent.md) - Logger configuration
+
+---
+
+_Last Updated: 2025-11-26_
+
diff --git a/packages/contact-center/ui-logging/package.json b/packages/contact-center/ui-logging/package.json
index dbaa09a38..deef6d9a1 100644
--- a/packages/contact-center/ui-logging/package.json
+++ b/packages/contact-center/ui-logging/package.json
@@ -41,4 +41,4 @@
"react": ">=18.3.1",
"react-dom": ">=18.3.1"
}
-}
\ No newline at end of file
+}
diff --git a/packages/contact-center/user-state/ai-docs/agent.md b/packages/contact-center/user-state/ai-docs/agent.md
new file mode 100644
index 000000000..32dd359c6
--- /dev/null
+++ b/packages/contact-center/user-state/ai-docs/agent.md
@@ -0,0 +1,381 @@
+# User State Widget
+
+## Overview
+
+The User State widget provides a user interface for contact center agents to manage their availability status. It handles agent state transitions (Available, Idle with various reasons), displays state duration timers, and manages idle code selection. The widget integrates with the Webex Contact Center SDK and follows the standard three-layer architecture pattern (Widget → Hook → Component → Store).
+
+**Package:** `@webex/cc-user-state`
+
+**Version:** See [package.json](../package.json)
+
+---
+
+## Why and What is This Widget Used For?
+
+### Purpose
+
+The User State widget enables contact center agents to:
+- **Change their availability state** (Available, Idle, etc.)
+- **Select idle codes** for different idle reasons (Break, Lunch, Meeting, etc.)
+- **View state duration** with real-time timer display
+- **Track idle code duration** separately from overall state duration
+- **Receive state change notifications** via callbacks
+
+### Key Capabilities
+
+- **State Management**: Toggle between Available and various Idle states
+- **Idle Code Support**: Dropdown selection for different idle reasons
+- **Real-Time Timers**: Displays elapsed time in current state using Web Worker
+- **Dual Timer Support**: Tracks both state duration and idle code duration
+- **State Change Callbacks**: Extensible callback for state change events
+- **Automatic State Sync**: Syncs state changes with backend via SDK
+- **Error Handling**: Comprehensive error boundary with callback support
+
+---
+
+## Examples and Use Cases
+
+### Getting Started
+
+#### Basic Usage (React)
+
+```typescript
+import { UserState } from '@webex/cc-user-state';
+import React from 'react';
+
+function MyApp() {
+ const handleStateChange = (newState) => {
+ console.log('Agent state changed to:', newState);
+ // Update your application state
+ };
+
+ return (
+
+ );
+}
+```
+
+#### Web Component Usage
+
+```html
+
+
+
+
+
+
+
+```
+
+#### With State Change Tracking
+
+```typescript
+import { UserState } from '@webex/cc-user-state';
+import React, { useState } from 'react';
+
+function AgentDashboard() {
+ const [currentState, setCurrentState] = useState(null);
+ const [stateHistory, setStateHistory] = useState([]);
+
+ const handleStateChange = (newState) => {
+ setCurrentState(newState);
+ setStateHistory(prev => [...prev, {
+ state: newState,
+ timestamp: new Date()
+ }]);
+ console.log('State changed to:', newState.name);
+ };
+
+ return (
+
+
Current State: {currentState?.name || 'Unknown'}
+
+
+
+
State History
+ {stateHistory.map((entry, idx) => (
+
+ {entry.state.name} - {entry.timestamp.toLocaleString()}
+
+ ))}
+
+
+ );
+}
+```
+
+### Common Use Cases
+
+#### 1. Agent Status Display
+
+```typescript
+// Display agent's current status with timer
+import { UserState } from '@webex/cc-user-state';
+import { observer } from 'mobx-react-lite';
+import store from '@webex/cc-store';
+
+const AgentStatusPanel = observer(() => {
+ const { currentState, idleCodes } = store;
+
+ const handleStateChange = (newState) => {
+ console.log(`Agent changed to: ${newState.name}`);
+ // Store automatically updates
+ };
+
+ return (
+
+
Your Status
+
+
Current: {currentState}
+
+ );
+});
+```
+
+#### 2. Supervisor Dashboard Integration
+
+```typescript
+// Track multiple agents' state changes
+import { UserState } from '@webex/cc-user-state';
+
+function SupervisorDashboard({ agentId }) {
+ const handleAgentStateChange = (state) => {
+ // Send state change to analytics
+ trackAgentState(agentId, state);
+
+ // Update dashboard
+ updateAgentStatus(agentId, state.name);
+
+ // Check if agent needs assistance
+ if (state.name === 'Idle' && state.duration > 900) {
+ notifySupervisor(`Agent ${agentId} idle for 15+ minutes`);
+ }
+ };
+
+ return ;
+}
+```
+
+#### 3. State Change Validation
+
+```typescript
+// Validate state changes before allowing
+import { UserState } from '@webex/cc-user-state';
+import store from '@webex/cc-store';
+
+function ValidatedUserState() {
+ const handleStateChange = (newState) => {
+ // Check if agent has pending tasks
+ if (newState.name === 'Idle' && store.hasActiveTasks) {
+ showWarning('Please complete active tasks before going idle');
+ // State change will be rejected by store
+ return;
+ }
+
+ // Log state change
+ auditLog('State change', {
+ agent: store.agentId,
+ from: store.currentState,
+ to: newState.name,
+ timestamp: Date.now()
+ });
+
+ console.log('State change approved:', newState);
+ };
+
+ return ;
+}
+```
+
+#### 4. Custom Error Handling
+
+```typescript
+import store from '@webex/cc-store';
+
+// Set error callback before rendering widget
+store.onErrorCallback = (componentName, error) => {
+ console.error(`Error in ${componentName}:`, error);
+
+ // Notify user
+ showErrorNotification('Failed to update state. Please try again.');
+
+ // Send to error tracking
+ trackError(componentName, error);
+};
+
+// Widget will call this callback on errors
+
+```
+
+### Integration Patterns
+
+#### With Agent Desktop
+
+```typescript
+import { UserState } from '@webex/cc-user-state';
+import { observer } from 'mobx-react-lite';
+import store from '@webex/cc-store';
+
+const AgentDesktop = observer(() => {
+ const { isAgentLoggedIn, currentState } = store;
+
+ const handleStateChange = (newState) => {
+ // Update local UI
+ updateHeaderStatus(newState.name);
+
+ // Log to analytics
+ logStateChange(newState);
+ };
+
+ if (!isAgentLoggedIn) {
+ return ;
+ }
+
+ return (
+
+
+
+
+
+
+
+ );
+});
+```
+
+#### With Idle Code Management
+
+```typescript
+import { UserState } from '@webex/cc-user-state';
+import { observer } from 'mobx-react-lite';
+import store from '@webex/cc-store';
+
+const StateManager = observer(() => {
+ const { idleCodes, currentState } = store;
+
+ const handleStateChange = (state) => {
+ console.log('State changed:', state);
+
+ // Log idle code if applicable
+ if (state.name !== 'Available' && 'id' in state) {
+ console.log('Idle code selected:', state.id);
+ logIdleCode(state.id, state.name);
+ }
+ };
+
+ return (
+
+
+
+
+
Available idle codes: {idleCodes.length}
+
Current state: {currentState}
+
+
+ );
+});
+```
+
+---
+
+## Dependencies
+
+**Note:** For exact versions, see [package.json](../package.json)
+
+### Runtime Dependencies
+
+| Package | Purpose |
+|---------|---------|
+| `@webex/cc-components` | React UI components |
+| `@webex/cc-store` | MobX singleton store for state management |
+| `mobx-react-lite` | React bindings for MobX |
+| `react-error-boundary` | Error boundary implementation |
+| `typescript` | TypeScript support |
+
+### Peer Dependencies
+
+| Package | Purpose |
+|---------|---------|
+| `react` | React framework |
+| `react-dom` | React DOM rendering |
+
+### Development Dependencies
+
+Key development tools (see [package.json](../package.json) for versions):
+- TypeScript
+- Jest (testing)
+- Webpack (bundling)
+- ESLint (linting)
+
+### External SDK Dependency
+
+The widget requires the **Webex Contact Center SDK** (`@webex/contact-center`) to be initialized and available through the store. The SDK provides:
+- `setAgentState()` - Updates agent state (Available/Idle)
+- Agent state events - Notifies on state changes
+- Idle code management - Provides available idle codes
+
+### Web Worker
+
+The widget uses a **Web Worker** for accurate timer management:
+- Runs timer in background thread
+- Prevents main thread blocking
+- Ensures accurate elapsed time tracking
+- Automatically cleaned up on unmount
+
+---
+
+## Props API
+
+| Prop | Type | Required | Default | Description |
+|------|------|----------|---------|-------------|
+| `onStateChange` | `(state: IdleCode \| CustomState) => void` | No | - | Callback when agent state changes |
+
+**State Object:**
+
+```typescript
+// IdleCode (from idle codes list)
+interface IdleCode {
+ id: string; // Idle code ID
+ name: string; // Display name (e.g., "Break", "Lunch")
+ // ... other properties
+}
+
+// CustomState (for custom states)
+interface CustomState {
+ developerName: string;
+ // ... custom properties
+}
+```
+
+---
+
+## Installation
+
+```bash
+# Install as part of contact center widgets
+yarn add @webex/cc-user-state
+
+# Or install the entire widgets bundle
+yarn add @webex/cc-widgets
+```
+
+---
+
+## Additional Resources
+
+For detailed component architecture, data flows, and sequence diagrams, see [architecture.md](./architecture.md).
+
+---
+
+_Last Updated: 2025-11-26_
+
diff --git a/packages/contact-center/user-state/ai-docs/architecture.md b/packages/contact-center/user-state/ai-docs/architecture.md
new file mode 100644
index 000000000..88c53b879
--- /dev/null
+++ b/packages/contact-center/user-state/ai-docs/architecture.md
@@ -0,0 +1,562 @@
+# User State Widget - Architecture
+
+## Component Overview
+
+The User State widget follows the three-layer architecture pattern: **Widget → Hook → Component → Store → SDK**. This architecture separates concerns between state management, business logic, and presentation. The widget uniquely uses a **Web Worker** for accurate timer management.
+
+### Component Table
+
+| Layer | Component | File | Config/Props | State | Callbacks | Events | Tests |
+|-------|-----------|------|--------------|-------|-----------|--------|-------|
+| **Widget** | `UserState` | `src/user-state/index.tsx` | `IUserStateProps` | N/A (passes through) | `onStateChange` | SDK events (via store) | `tests/user-state/index.tsx` |
+| **Widget Internal** | `UserStateInternal` | `src/user-state/index.tsx` | `IUserStateProps` | Observes store | Same as above | Same as above | Same |
+| **Hook** | `useUserState` | `src/helper.ts` | `UseUserStateProps` | `isSettingAgentStatus`, `elapsedTime`, `lastIdleStateChangeElapsedTime` | `onStateChange` | Web Worker messages | `tests/helper.ts` |
+| **Web Worker** | Timer Worker | `src/helper.ts` (inline) | Worker messages | `intervalId`, `intervalId2` (timers) | N/A | `elapsedTime`, `lastIdleStateChangeElapsedTime` | N/A |
+| **Component** | `UserStateComponent` | `@webex/cc-components` | `UserStateComponentsProps` | Internal UI state | Inherited from hook | N/A | `@webex/cc-components` tests |
+| **Store** | `Store` (singleton) | `@webex/cc-store` | N/A | `idleCodes`, `agentId`, `currentState`, `lastStateChangeTimestamp`, `lastIdleCodeChangeTimestamp`, `customState` | N/A | Agent state change events | `@webex/cc-store` tests |
+| **SDK** | `ContactCenter` | `@webex/contact-center` | N/A | N/A | N/A | State change events | SDK tests |
+
+### SDK Methods & Events Integration
+
+| Component | SDK Methods Used | SDK Events Subscribed | Store Methods Used |
+|-----------|------------------|----------------------|-------------------|
+| **useUserState Hook** | `setAgentState()` | Agent state change events | `setCurrentState()`, `setLastStateChangeTimestamp()`, `setLastIdleCodeChangeTimestamp()` |
+| **Store** | All SDK methods | All SDK events | N/A |
+| **Widget** | N/A (via hook) | N/A (via store) | N/A (via hook) |
+
+### File Structure
+
+```
+user-state/
+├── src/
+│ ├── helper.ts # useUserState hook + Web Worker
+│ ├── index.ts # Package exports
+│ ├── user-state/
+│ │ └── index.tsx # Widget component
+│ └── user-state.types.ts # TypeScript types
+├── tests/
+│ ├── helper.ts # Hook tests
+│ └── user-state/
+│ └── index.tsx # Widget tests
+├── ai-docs/
+│ ├── agent.md # Overview, examples, usage
+│ └── architecture.md # Architecture documentation
+├── dist/ # Build output
+├── package.json # Dependencies and scripts
+├── tsconfig.json # TypeScript config
+├── webpack.config.js # Webpack build config
+├── jest.config.js # Jest test config
+└── eslint.config.mjs # ESLint config
+```
+
+---
+
+## Data Flows
+
+### Layer Communication Flow
+
+The widget follows a unidirectional data flow pattern across layers with Web Worker integration:
+
+```mermaid
+graph TB
+ subgraph "Presentation Layer"
+ Widget[UserState Widget]
+ Component[UserStateComponent]
+ end
+
+ subgraph "Business Logic Layer"
+ Hook[useUserState Hook
helper.ts]
+ end
+
+ subgraph "Background Processing"
+ Worker[Web Worker
Timer]
+ end
+
+ subgraph "State Management Layer"
+ Store[Store Singleton]
+ end
+
+ subgraph "SDK Layer"
+ SDK[Contact Center SDK]
+ end
+
+ Widget -->|Props
callbacks| Hook
+ Hook -->|Read state
idleCodes, currentState, etc| Store
+ Hook -->|Call methods
setAgentState| SDK
+ Store -->|Register callbacks
Manage SDK instance| SDK
+ Hook <-->|Start/Stop/Reset timer| Worker
+
+ Worker -->|Timer updates
every second| Hook
+ SDK -->|Events
state changes| Store
+ Store -->|State changes
observable| Hook
+ Hook -->|Return state
& handlers & timer| Widget
+ Widget -->|Props
state, handlers, timer| Component
+
+ style Hook fill:#e1f5ff
+ style Worker fill:#ffe1e1
+ style Store fill:#fff4e1
+ style SDK fill:#f0e1ff
+```
+
+**Hook Responsibilities:**
+- Manages timer via Web Worker
+- Subscribes to state changes
+- Handles state update logic
+- Dual timer management
+- Error handling
+
+**Web Worker Responsibilities:**
+- Background timer execution
+- Two independent timers
+- State duration timer
+- Idle code duration timer
+
+**Store Responsibilities:**
+- Observable state
+- Idle codes list
+- Current state tracking
+- Timestamps for timers
+
+### Hook (helper.ts) Details
+
+**File:** `src/helper.ts`
+
+The `useUserState` hook is the core business logic layer that:
+
+1. **Manages Local State:**
+ - `isSettingAgentStatus` - Loading indicator during state change
+ - `elapsedTime` - Seconds elapsed in current state
+ - `lastIdleStateChangeElapsedTime` - Seconds elapsed since idle code change
+
+2. **Web Worker Management:**
+ ```typescript
+ // Initialize Web Worker with inline script
+ const blob = new Blob([workerScript], {type: 'application/javascript'});
+ const workerUrl = URL.createObjectURL(blob);
+ workerRef.current = new Worker(workerUrl);
+
+ // Start both timers
+ workerRef.current.postMessage({type: 'start', startTime: Date.now()});
+ workerRef.current.postMessage({type: 'startIdleCode', startTime: Date.now()});
+ ```
+
+3. **Provides Key Functions:**
+ - `setAgentStatus()` - Updates store with new state (UI trigger)
+ - `updateAgentState()` - Calls SDK to persist state change (Backend sync)
+ - `callOnStateChange()` - Invokes callback with current state
+
+4. **State Change Logic:**
+ - UI triggers `setAgentStatus()` → Updates store
+ - Store change triggers `useEffect` → Calls `updateAgentState()`
+ - `updateAgentState()` → SDK call → Updates timestamps → Timer reset
+ - Callback invoked with new state object
+
+5. **Timer Reset Logic:**
+ - Resets main timer when `lastStateChangeTimestamp` changes
+ - Resets idle code timer when `lastIdleCodeChangeTimestamp` changes
+ - Stops idle code timer if timestamp matches state timestamp (Available state)
+
+### Sequence Diagrams
+
+#### 1. Widget Initialization & Timer Start
+
+```mermaid
+sequenceDiagram
+ actor User
+ participant Widget as UserState Widget
+ participant Hook as useUserState Hook
+ participant Worker as Web Worker
+ participant Component as UserStateComponent
+ participant Store
+
+ User->>Widget: Load widget
+ activate Widget
+ Widget->>Hook: useUserState()
+ activate Hook
+ Hook->>Store: Read state
+ Store-->>Hook: {idleCodes, currentState, timestamps}
+ Hook->>Worker: Create & initialize
+ activate Worker
+ Hook->>Worker: postMessage({type: 'start', startTime})
+ Hook->>Worker: postMessage({type: 'startIdleCode', startTime})
+ Worker-->>Hook: Worker ready
+ Hook-->>Widget: {state, handlers, timers}
+ deactivate Hook
+ Widget->>Component: Render with state
+ activate Component
+ Component->>Component: Display current state
+ Component->>Component: Display idle codes
+ Component->>Component: Display timer: 00:00
+ Component-->>Widget: UI rendered
+ deactivate Component
+ deactivate Widget
+
+ Note over Worker,Component: Timer Updates (Every Second)
+ Worker->>Worker: Increment timers
+ Worker->>Hook: postMessage({type: 'elapsedTime', elapsedTime: X})
+ activate Hook
+ Hook->>Hook: setElapsedTime(X)
+ Hook-->>Component: Updated timer value
+ deactivate Hook
+ activate Component
+ Component->>Component: Display timer: 00:0X
+ deactivate Component
+```
+
+---
+
+#### 2. State Change Flow
+
+```mermaid
+sequenceDiagram
+ actor User
+ participant Component as UserStateComponent
+ participant Hook as useUserState Hook
+ participant Store
+ participant SDK
+ participant Worker as Web Worker
+
+ User->>Component: Select new state (e.g., "Break")
+ activate Component
+ Component->>Hook: setAgentStatus(selectedCode)
+ activate Hook
+ Hook->>Store: setCurrentState(selectedCode)
+ activate Store
+ Store->>Store: currentState = selectedCode (observable)
+ Store-->>Hook: State updated
+ deactivate Store
+ Hook-->>Component: State change initiated
+ deactivate Hook
+ deactivate Component
+
+ Note over Hook,SDK: useEffect Triggered by currentState Change
+ Hook->>Hook: Detect currentState change
+ activate Hook
+ Hook->>Hook: updateAgentState(selectedCode)
+ Hook->>Hook: setIsSettingAgentStatus(true)
+ Hook->>SDK: setAgentState({state: 'Idle', auxCodeId, agentId})
+ activate SDK
+ SDK->>SDK: Update agent state in backend
+ SDK-->>Hook: Success response with timestamps
+ deactivate SDK
+ Hook->>Store: setLastStateChangeTimestamp(timestamp)
+ activate Store
+ Store->>Store: Update timestamps
+ Store-->>Hook: Timestamps updated
+ deactivate Store
+ Hook->>Hook: setIsSettingAgentStatus(false)
+ Hook->>Hook: callOnStateChange()
+ Hook->>Hook: Invoke onStateChange(selectedCode)
+ Hook-->>Component: State change complete
+ deactivate Hook
+ activate Component
+ Component->>Component: Update UI with new state
+ deactivate Component
+
+ Note over Hook,Worker: Timer Reset
+ Hook->>Hook: Detect timestamp change
+ activate Hook
+ Hook->>Worker: postMessage({type: 'reset', startTime})
+ activate Worker
+ Worker->>Worker: Clear old interval
+ Worker->>Worker: Start new interval
+ Worker-->>Hook: Timer reset
+ deactivate Worker
+ Hook-->>Component: Timer reset to 00:00
+ deactivate Hook
+ activate Component
+ Component->>Component: Display timer: 00:00
+ deactivate Component
+```
+
+---
+
+#### 3. Custom State Change Flow
+
+```mermaid
+sequenceDiagram
+ actor User
+ participant Component as UserStateComponent
+ participant Hook as useUserState Hook
+ participant Store
+
+ User->>Component: External state change
+ activate Component
+ Component->>Component: (State managed externally)
+ deactivate Component
+
+ Store->>Store: customState updated (external)
+ activate Store
+ Store-->>Hook: customState change (observable)
+ deactivate Store
+
+ Note over Hook: useEffect Triggered by customState Change
+ Hook->>Hook: Detect customState change
+ activate Hook
+ Hook->>Hook: callOnStateChange()
+ Hook->>Hook: Check if customState has developerName
+
+ alt customState has developerName
+ Hook->>Hook: onStateChange(customState)
+ else no developerName
+ Hook->>Hook: Find matching idle code
+ Hook->>Hook: onStateChange(matchingCode)
+ end
+
+ Hook-->>Component: Callback invoked
+ deactivate Hook
+ activate Component
+ Component->>Component: Handle custom state
+ deactivate Component
+```
+
+---
+
+#### 4. Cleanup & Worker Termination
+
+```mermaid
+sequenceDiagram
+ actor User
+ participant Widget as UserState Widget
+ participant Hook as useUserState Hook
+ participant Worker as Web Worker
+
+ User->>Widget: Unmount widget
+ activate Widget
+ Widget->>Hook: Cleanup (useEffect return)
+ activate Hook
+ Hook->>Worker: postMessage({type: 'stop'})
+ activate Worker
+ Worker->>Worker: clearInterval(intervalId)
+ Worker-->>Hook: Timer stopped
+ deactivate Worker
+ Hook->>Worker: postMessage({type: 'stopIdleCode'})
+ activate Worker
+ Worker->>Worker: clearInterval(intervalId2)
+ Worker-->>Hook: Idle timer stopped
+ deactivate Worker
+ Hook->>Worker: terminate()
+ activate Worker
+ Worker->>Worker: Cleanup resources
+ Worker-->>Hook: Worker terminated
+ deactivate Worker
+ Hook->>Hook: workerRef.current = null
+ Hook-->>Widget: Cleanup complete
+ deactivate Hook
+ Widget-->>User: Widget unmounted
+ deactivate Widget
+```
+
+---
+
+## Troubleshooting Guide
+
+### Common Issues
+
+#### 1. Timer Not Updating
+
+**Symptoms:**
+- Timer shows 00:00 and doesn't increment
+- Timer freezes after state change
+
+**Possible Causes:**
+- Web Worker failed to initialize
+- Browser doesn't support Web Workers
+- Worker messages not being received
+
+**Solutions:**
+
+```typescript
+// Check if Web Worker is supported
+if (typeof Worker === 'undefined') {
+ console.error('Web Workers not supported in this browser');
+}
+
+// Verify worker initialization in console
+console.log('Worker ref:', workerRef.current);
+
+// Check worker messages
+workerRef.current.onmessage = (event) => {
+ console.log('Worker message:', event.data);
+};
+
+// Manually trigger timer update for testing
+store.setLastStateChangeTimestamp(Date.now());
+```
+
+#### 2. State Change Not Persisting
+
+**Symptoms:**
+- UI shows new state but reverts back
+- SDK call fails silently
+- `isSettingAgentStatus` stays true
+
+**Possible Causes:**
+- SDK not initialized
+- Invalid state or idle code
+- Network issues
+- Agent not logged in
+
+**Solutions:**
+
+```typescript
+// Check SDK instance
+console.log('CC instance:', store.cc);
+
+// Verify agent is logged in
+console.log('Agent logged in:', store.isAgentLoggedIn);
+
+// Check current state
+console.log('Current state:', store.currentState);
+
+// Check idle codes availability
+console.log('Idle codes:', store.idleCodes);
+
+// Enable detailed logging
+store.logger.setLevel('debug');
+```
+
+#### 3. Idle Codes Not Displaying
+
+**Symptoms:**
+- Dropdown is empty
+- No idle codes available
+- Only "Available" shows
+
+**Possible Causes:**
+- Idle codes not loaded from backend
+- Store not initialized properly
+- Configuration issue
+
+**Solutions:**
+
+```typescript
+// Check idle codes in store
+import store from '@webex/cc-store';
+console.log('Idle codes:', store.idleCodes);
+console.log('Idle codes count:', store.idleCodes.length);
+
+// Verify store initialization
+console.log('Store initialized:', store.cc !== undefined);
+
+// Check agent configuration
+console.log('Agent config:', store.cc?.agentConfig);
+```
+
+#### 4. Callback Not Firing
+
+**Symptoms:**
+- `onStateChange` not called
+- No notification on state change
+- State changes but app doesn't update
+
+**Possible Causes:**
+- Callback not provided
+- Callback reference changing
+- Error in callback execution
+
+**Solutions:**
+
+```typescript
+// Ensure callback is stable
+const handleStateChange = useCallback((state) => {
+ console.log('State changed:', state);
+}, []);
+
+// Wrap in try-catch
+const handleStateChange = (state) => {
+ try {
+ console.log('State changed:', state);
+ // Your logic here
+ } catch (error) {
+ console.error('Error in state change callback:', error);
+ }
+};
+
+// Verify callback is passed
+
+
+// Check if callback is being invoked (add logging in hook)
+console.log('Calling onStateChange with:', state);
+onStateChange?.(state);
+```
+
+#### 5. Memory Leak with Worker
+
+**Symptoms:**
+- Browser tab becomes slow over time
+- Multiple workers running
+- Memory usage increases
+
+**Possible Causes:**
+- Worker not terminated on unmount
+- Multiple widget instances
+- Worker cleanup not executed
+
+**Solutions:**
+
+```typescript
+// Verify cleanup on unmount
+useEffect(() => {
+ // ... worker initialization
+
+ return () => {
+ console.log('Cleaning up worker');
+ if (workerRef.current) {
+ workerRef.current.terminate();
+ workerRef.current = null;
+ }
+ };
+}, []);
+
+// Check for multiple widget instances
+// Only render one UserState widget at a time
+
+// Monitor worker instances in DevTools
+// Performance > Memory > Take snapshot
+```
+
+#### 6. Dual Timer Mismatch
+
+**Symptoms:**
+- State timer and idle code timer show different values
+- Timers not in sync
+- Idle code timer doesn't stop on Available
+
+**Possible Causes:**
+- Timestamps not set correctly
+- Worker messages mixed up
+- Logic error in timer reset
+
+**Solutions:**
+
+```typescript
+// Check timestamps
+console.log('State timestamp:', store.lastStateChangeTimestamp);
+console.log('Idle code timestamp:', store.lastIdleCodeChangeTimestamp);
+
+// Verify timer values
+console.log('Elapsed time:', elapsedTime);
+console.log('Idle elapsed time:', lastIdleStateChangeElapsedTime);
+
+// Check if idle timer should be stopped
+if (currentState === 'Available') {
+ console.log('Idle timer should be stopped');
+ // Should show -1 or 0
+}
+```
+
+---
+
+## Related Documentation
+
+- [Agent Documentation](./agent.md) - Usage examples and props
+- [MobX Patterns](../../../../ai-docs/patterns/mobx-patterns.md) - Store patterns
+- [React Patterns](../../../../ai-docs/patterns/react-patterns.md) - Component patterns
+- [Testing Patterns](../../../../ai-docs/patterns/testing-patterns.md) - Testing guidelines
+- [Store Documentation](../../store/ai-docs/agent.md) - Store API reference
+
+---
+
+_Last Updated: 2025-11-26_
+
diff --git a/packages/contact-center/user-state/package.json b/packages/contact-center/user-state/package.json
index 68ef0ad1c..95dcb236a 100644
--- a/packages/contact-center/user-state/package.json
+++ b/packages/contact-center/user-state/package.json
@@ -65,4 +65,4 @@
"react": ">=18.3.1",
"react-dom": ">=18.3.1"
}
-}
\ No newline at end of file
+}
diff --git a/tooling.md b/tooling.md
new file mode 100644
index 000000000..80abe68ca
--- /dev/null
+++ b/tooling.md
@@ -0,0 +1,35 @@
+# Tooling
+
+## Overview
+
+- Monorepo with per-package build configs (`webpack.config.js`, `rollup.config.js` where applicable)
+- TypeScript per package (`tsconfig.json`, `tsconfig.test.json`)
+- Linting via package-local `eslint.config.mjs` (where present)
+- Testing via Jest (unit/integration) and Playwright (E2E)
+- Detailed docs live at `./ai-docs/toolings/tooling.md`
+
+## Commands & scripts
+
+- Commands are defined per package in their `package.json`.
+
+
+## Local dev workflow
+
+- Install dependencies at repo root using the configured package manager
+- Build and test at the package level
+- For E2E, use Playwright with tests in `playwright/`
+
+## CI/CD
+
+- Each package contains Jest configs and tsconfig for CI execution
+
+
+## Troubleshooting
+
+- Clean installs if builds fail; verify local environment for Playwright
+
+See the full details and examples in:
+
+- `./ai-docs/toolings/tooling.md`
+
+
diff --git a/widgets-samples/cc/samples-cc-react-app/ai-docs/agent.md b/widgets-samples/cc/samples-cc-react-app/ai-docs/agent.md
new file mode 100644
index 000000000..346249c16
--- /dev/null
+++ b/widgets-samples/cc/samples-cc-react-app/ai-docs/agent.md
@@ -0,0 +1,365 @@
+# React Sample App - Widget Integration Guide
+
+## Purpose
+
+Demonstrates how to integrate Contact Center widgets into a React application. Use this as a reference when adding new widgets.
+
+## Design System
+
+- **Framework:** React 18.3.1 + TypeScript
+- **UI Library:** Momentum Design System (`@momentum-design/components`)
+- **State:** MobX store (`@webex/cc-store`)
+- **Theme:** Dark/Light mode toggle (persisted in localStorage)
+- **Layout:** CSS Grid with `.box`, `.section-box`, `.fieldset` structure
+
+## Critical Integration Pattern
+
+**Follow this exact pattern for ALL new widgets:**
+
+### Step 1: Import the Widget
+
+```tsx
+import { NewWidget } from '@webex/cc-widgets';
+```
+
+**Add to imports section (lines 1-28)** with other widgets.
+
+### Step 2: Add to defaultWidgets
+
+```tsx
+const defaultWidgets = {
+ stationLogin: true,
+ userState: true,
+ // ... existing widgets
+ newWidget: false, // ← Add here (false by default for user opt-in)
+};
+```
+
+**Location:** Line 33-42 in `App.tsx`
+
+### Step 3: Add Checkbox for Widget Selection
+
+```tsx
+
+ New Widget
+
+```
+
+**Location:** Widget selector section in render (around line 300-400)
+
+### Step 4: Conditional Rendering with Standard Layout
+
+```tsx
+{selectedWidgets.newWidget && (
+
+
+
+
+
+)}
+```
+
+**Location:** Main render section, grouped by widget category
+
+### Step 5: Add Event Handlers (if needed)
+
+```tsx
+const handleNewWidgetEvent = (data) => {
+ console.log('New widget event:', data);
+ // Handle event logic
+};
+```
+
+**Location:** With other event handlers in component
+
+## Layout Structure Rules
+
+### Container Hierarchy (ALWAYS use this)
+
+```tsx
+ {/* Outer container with background */}
+ {/* Inner section with padding */}
+
+
+
+```
+
+### Why this structure?
+
+- `box` - Consistent spacing and background
+- `section-box` - Momentum Design padding
+- `fieldset` - Semantic grouping
+- `legend-box` - Styled title
+- **Result:** Visual consistency across all widgets
+
+## Styling Rules
+
+### CSS Variables (MUST USE)
+
+```scss
+// Colors
+var(--mds-color-theme-text-primary-normal)
+var(--mds-color-theme-background-solid-primary-normal)
+var(--mds-color-theme-background-primary-normal)
+
+// Spacing
+var(--mds-spacing-1) // 0.25rem (4px)
+var(--mds-spacing-2) // 0.5rem (8px)
+var(--mds-spacing-3) // 1rem (16px)
+var(--mds-spacing-4) // 1.5rem (24px)
+
+// Typography
+var(--mds-font-size-body-small)
+var(--mds-font-size-body-medium)
+```
+
+### ❌ NEVER Do This
+
+```tsx
+ // Hardcoded color
+
// Hardcoded spacing
+```
+
+### ✅ ALWAYS Do This
+
+```tsx
+
+```
+
+## Event Handling Pattern
+
+### Standard onError Handler
+
+```tsx
+const onError = (source: string, error: Error) => {
+ console.error(`${source} error:`, error);
+ // Optional: Show toast notification
+ setToast({ type: 'error' });
+};
+```
+
+**EVERY widget MUST have onError callback.**
+
+### Widget-Specific Events
+
+```tsx
+// IncomingTask
+const onIncomingTaskCB = ({ task }) => {
+ console.log('Incoming task:', task);
+ setIncomingTasks(prev => [...prev, task]);
+ playNotificationSound(); // Custom logic
+};
+
+// UserState
+const onAgentStateChangedCB = (newState: AgentState, oldState: AgentState) => {
+ console.log('State changed from', oldState, 'to', newState);
+ setSelectedState(newState);
+};
+
+// CallControl
+const onRecordingToggleCB = ({ isRecording, task }) => {
+ console.log('Recording:', isRecording, 'for task:', task.data.interactionId);
+};
+```
+
+## Theme Integration
+
+Widgets automatically use MobX store theme:
+
+```tsx
+// Theme is managed by store.currentTheme
+// Widget CSS uses CSS variables that respond to theme changes
+// No manual theme passing needed
+```
+
+**User can toggle theme via UI dropdown** - widgets update automatically.
+
+## State Management
+
+### When to Use store Directly
+
+```tsx
+// Access store for global state
+import { store } from '@webex/cc-widgets';
+
+// Examples:
+store.currentTask // Current active task
+store.taskList // All tasks
+store.incomingTask // Incoming task
+store.agentState // Current agent state
+```
+
+### When to Use Local State
+
+```tsx
+// UI-only state (no widget dependency)
+const [showPopup, setShowPopup] = useState(false);
+const [selectedOption, setSelectedOption] = useState('');
+```
+
+## Complete Example: Adding a New Widget
+
+```tsx
+// 1. Import
+import { NewAwesomeWidget } from '@webex/cc-widgets';
+
+// 2. Add to defaultWidgets
+const defaultWidgets = {
+ // ... existing
+ newAwesomeWidget: false,
+};
+
+// 3. Checkbox in widget selector
+
+ New Awesome Widget
+
+
+// 4. Event handler (if needed)
+const handleAwesomeEvent = (data) => {
+ console.log('Awesome event:', data);
+};
+
+// 5. Render with standard layout
+{selectedWidgets.newAwesomeWidget && (
+
+
+
+
+
+)}
+```
+
+## Common Mistakes to AVOID
+
+### ❌ Breaking CSS class structure
+
+```tsx
+// WRONG
+
+
+
+```
+
+### ✅ Correct
+
+```tsx
+
+
+
+```
+
+### ❌ Forgetting defaultWidgets entry
+
+```tsx
+// WRONG - Widget renders immediately, user can't disable
+{selectedWidgets.newWidget &&
}
+// But newWidget not in defaultWidgets!
+```
+
+### ✅ Correct
+
+```tsx
+// In defaultWidgets
+const defaultWidgets = {
+ newWidget: false, // ← MUST ADD HERE
+};
+
+// Then render
+{selectedWidgets.newWidget &&
}
+```
+
+### ❌ Missing error handler
+
+```tsx
+// WRONG
+
+```
+
+### ✅ Correct
+
+```tsx
+
onError('NewWidget', error)}
+/>
+```
+
+### ❌ Hardcoding colors
+
+```tsx
+// WRONG
+
+```
+
+### ✅ Correct
+
+```tsx
+
+```
+
+## Testing Checklist
+
+After adding a new widget:
+
+- [ ] Widget imports without errors
+- [ ] Appears in widget selector checkbox list
+- [ ] Can be enabled/disabled via checkbox
+- [ ] Selection persists in localStorage
+- [ ] Renders with correct layout (box > section-box > fieldset)
+- [ ] Has legend/title
+- [ ] Uses Momentum CSS variables (no hardcoded colors)
+- [ ] Event handlers fire correctly
+- [ ] onError handler present and logs errors
+- [ ] Works in both light and dark themes
+- [ ] No console errors when enabled/disabled
+- [ ] No visual/layout breaking when rendered alongside other widgets
+
+## File Locations
+
+- **Main App:** `src/App.tsx`
+- **Styles:** `src/App.scss`
+- **Widget Imports:** Line 1-28 in `App.tsx`
+- **defaultWidgets:** Line 33-42 in `App.tsx`
+- **Widget Selector:** Around line 300-400 in render method
+- **Widget Render:** Main render section grouped by category
+
+## Additional Resources
+
+- [Momentum Design System Docs](https://momentum.design/)
+- [MobX Store Package](../../packages/contact-center/store/ai-docs/agent.md)
+- [cc-widgets Package](../../packages/contact-center/cc-widgets/ai-docs/agent.md)
+
diff --git a/widgets-samples/cc/samples-cc-react-app/package.json b/widgets-samples/cc/samples-cc-react-app/package.json
index 5c78888bf..83887c195 100644
--- a/widgets-samples/cc/samples-cc-react-app/package.json
+++ b/widgets-samples/cc/samples-cc-react-app/package.json
@@ -25,7 +25,7 @@
"react-dom": "18.3.1",
"ts-loader": "^9.5.1",
"typescript": "^5.6.3",
- "webex": "3.9.0-next.30",
+ "webex": "link:/Users/rankush/dev/Cisco/ccSDK/webex-js-sdk/packages/webex",
"webpack": "^5.94.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^5.1.0",
diff --git a/widgets-samples/cc/samples-cc-react-app/src/App.tsx b/widgets-samples/cc/samples-cc-react-app/src/App.tsx
index f13443a17..7b71fed02 100644
--- a/widgets-samples/cc/samples-cc-react-app/src/App.tsx
+++ b/widgets-samples/cc/samples-cc-react-app/src/App.tsx
@@ -2,6 +2,7 @@ import React, {useState, useEffect} from 'react';
import {
StationLogin,
UserState,
+ CallHistory,
IncomingTask,
TaskList,
CallControl,
@@ -34,6 +35,7 @@ const defaultWidgets = {
stationLogin: true,
stationLoginProfile: false,
userState: true,
+ callHistory: false,
incomingTask: true,
taskList: true,
callControl: true,
@@ -373,6 +375,10 @@ function App() {
}
};
+ const onCallHistoryDial = (phoneNumber: string) => {
+ console.log('CallHistory dial requested:', phoneNumber);
+ };
+
const stationLogout = () => {
store.cc
.stationLogout({logoutReason: 'User requested logout'})
@@ -750,6 +756,20 @@ function App() {
)}
+ {selectedWidgets.callHistory && (
+
+
+
+
+
+ )}
+