diff --git a/packages/@webex/widgets/ai-docs/AGENTS.md b/packages/@webex/widgets/ai-docs/AGENTS.md
new file mode 100644
index 000000000..56dfbb969
--- /dev/null
+++ b/packages/@webex/widgets/ai-docs/AGENTS.md
@@ -0,0 +1,352 @@
+# Meetings Widget
+
+## AI Agent Routing Instructions
+
+**If you are an AI assistant or automated tool:**
+
+Do **not** use this file as your main entry point for reasoning or code generation.
+
+- **How to proceed:**
+ - Carefully load and follow the guidance, templates, and routing logic given in this `AGENTS.md`.
+
+Only after following the routing process laid out in the parent `AGENTS.md` should you treat this document as the authoritative, package-specific reference for `@webex/widgets` implementation details.
+
+## Overview
+
+The Meetings Widget provides a full-featured Webex meeting experience as an embeddable component. It orchestrates three external repositories — `webex-js-sdk` for backend communication, `sdk-component-adapter` for reactive data binding, and `components` for the React UI.
+
+**Widget:** Meetings
+
+**Package:** `@webex/widgets`
+
+**Version:** See [package.json](../package.json)
+
+**Location:** `packages/@webex/widgets`
+
+---
+
+## Why and What is This Used For?
+
+### Purpose
+
+The Meetings Widget lets consuming applications embed a complete meeting experience without building any meeting logic themselves. It handles the entire lifecycle — from SDK initialization through meeting creation, joining, in-meeting controls, and leaving — by composing existing components and adapters together.
+
+### Key Capabilities
+
+- **Join Meetings** — Connect to a meeting via URL, SIP address, or Personal Meeting Room
+- **Audio Controls** — Mute and unmute microphone with transitional states
+- **Video Controls** — Start and stop camera with device switching
+- **Screen Sharing** — Share screen, window, or tab with other participants
+- **Member Roster** — View list of meeting participants
+- **Device Settings** — Switch between cameras, microphones, and speakers
+- **Guest/Host Authentication** — Password-protected meetings with host key support
+- **Waiting for Host** — Automatic transition when host starts the meeting
+
+---
+
+## Examples and Use Cases
+
+### Getting Started
+
+#### Basic Usage (React)
+
+The widget handles SDK initialization, adapter creation, meeting creation, and all internal wiring via the `withAdapter` and `withMeeting` HOCs. Consumers just import and render with props:
+
+```jsx
+import {WebexMeetingsWidget} from '@webex/widgets';
+
+function App() {
+ return (
+
+ );
+}
+```
+
+#### With All Optional Props
+
+```jsx
+
+```
+
+#### What Happens Internally
+
+When `WebexMeetingsWidget` mounts, the `withAdapter` HOC:
+
+1. Creates a `Webex` instance using the `accessToken` prop
+2. Wraps it in a `WebexSDKAdapter`
+3. Calls `adapter.connect()` (registers device, opens WebSocket, syncs meetings)
+4. Provides the adapter via `AdapterContext`
+
+The `withMeeting` HOC then creates a meeting from `meetingDestination` and passes the meeting object as a prop. The widget renders the appropriate view based on meeting state.
+
+### Common Use Cases
+
+#### 1. Password-Protected Meeting
+
+When a meeting requires a password, the `WebexMeeting` component detects `passwordRequired` from the adapter observable and renders the `WebexMeetingGuestAuthentication` modal. The user enters the password, and `JoinControl.action()` passes it to the SDK.
+
+**Key Points:**
+
+- `passwordRequired` is a boolean on the adapter meeting observable
+- The component handles guest vs host authentication flows
+- Wrong password triggers `invalidPassword` flag on the observable
+
+#### 2. Pre-Join Media Preview
+
+Before joining, the interstitial screen shows local media preview. The user can mute audio, stop video, or open settings before entering the meeting.
+
+**Key Points:**
+
+- `WebexInterstitialMeeting` renders when `state === 'NOT_JOINED'`
+- Controls available pre-join: `mute-audio`, `mute-video`, `settings`, `join-meeting`
+- `JoinControl.display()` shows a hint like "Unmuted, video on" based on current state
+
+#### 3. Device Switching Mid-Meeting
+
+During an active meeting, users can switch cameras, microphones, or speakers through the settings panel.
+
+**Key Points:**
+
+- `SettingsControl.action({ meetingID })` opens the `WebexSettings` modal
+- `SwitchCameraControl.action({ meetingID, cameraId })` calls `switchCamera(meetingID, cameraId)` on the adapter
+- The adapter acquires a new media stream with the selected device and emits an updated `localVideo.stream`
+
+#### 4. Screen Sharing
+
+The share button triggers the browser's native screen picker. The SDK handles `getDisplayMedia()` and negotiates the share stream with the backend.
+
+**Key Points:**
+
+- `ShareControl` checks `navigator.mediaDevices.getDisplayMedia` availability
+- If unsupported, the control renders as DISABLED
+- The adapter emits `localShare.stream` with the display stream when sharing starts
+
+---
+
+## Three-Repository Architecture
+
+```mermaid
+graph LR
+ subgraph "Widget"
+ W[Meetings Widget]
+ end
+
+ subgraph "components"
+ C[WebexMeeting & UI]
+ end
+
+ subgraph "sdk-component-adapter"
+ A[MeetingsSDKAdapter]
+ end
+
+ subgraph "webex-js-sdk"
+ S[Webex Instance]
+ end
+
+ W -->|renders| C
+ W -->|creates| A
+ W -->|initializes| S
+ C -->|uses via AdapterContext| A
+ A -->|wraps| S
+
+ style W fill:#e1f5ff,color:#000
+ style C fill:#d4edda,color:#000
+ style A fill:#fff4e1,color:#000
+ style S fill:#ffe1e1,color:#000
+```
+
+
+
+
+| Repository | Role | Key Exports Used |
+| ----------------------- | ----------------------------------------- | ------------------------------------------------------------------- |
+| `webex-js-sdk` | Core SDK for Webex backend communication | `new Webex()`, `webex.meetings`, meeting methods |
+| `sdk-component-adapter` | Reactive adapter layer (RxJS observables) | `WebexSDKAdapter`, `MeetingsSDKAdapter`, all Control classes |
+| `components` | React UI components + hooks | `WebexMeeting`, `AdapterContext`, `useMeeting`, `useMeetingControl` |
+
+
+---
+
+## Dependencies
+
+**Note:** For exact versions, see [package.json](../package.json)
+
+### Runtime Dependencies
+
+
+| Package | Purpose |
+| ------------------------------------- | ----------------------------------------------------- |
+| `webex` | Core Webex JavaScript SDK for backend communication |
+| `@webex/sdk-component-adapter` | Reactive adapter that wraps SDK into RxJS observables |
+| `@webex/components` | React UI components for meeting views and controls |
+| `@webex/component-adapter-interfaces` | Interface definitions for component adapters |
+
+
+### Peer Dependencies
+
+
+| Package | Purpose |
+| ------------ | ------------------------ |
+| `react` | React framework |
+| `react-dom` | React DOM rendering |
+| `prop-types` | React prop type checking |
+| `webex` | Core Webex SDK (peer) |
+
+
+---
+
+## API Reference
+
+### WebexMeetingsWidget Props (Public API)
+
+These are the props consumers pass when using the widget. The widget handles SDK/adapter setup internally.
+
+
+| Prop | Type | Required | Default | Description |
+| ---------------------------- | ---------- | -------- | ----------- | ---------------------------------------------------------------------- |
+| `accessToken` | `string` | **Yes** | — | Webex access token for authentication |
+| `meetingDestination` | `string` | **Yes** | — | Meeting URL, SIP address, email, or Personal Meeting Room link |
+| `meetingPasswordOrPin` | `string` | No | `''` | Password or host pin for protected meetings |
+| `participantName` | `string` | No | `''` | Display name for guest participants |
+| `fedramp` | `bool` | No | `false` | Enable FedRAMP-compliant environment |
+| `layout` | `string` | No | `'Grid'` | Remote video layout (`Grid`, `Stack`, `Overlay`, `Prominent`, `Focus`) |
+| `controls` | `Function` | No | `undefined` | Function returning control IDs to render |
+| `controlsCollapseRangeStart` | `number` | No | `undefined` | Zero-based index of the first collapsible control |
+| `controlsCollapseRangeEnd` | `number` | No | `undefined` | Zero-based index before the last collapsible control |
+| `className` | `string` | No | `''` | Custom CSS class for the root element |
+| `style` | `object` | No | `{}` | Inline styles for the root element |
+
+
+**Source:** `src/widgets/WebexMeetings/WebexMeetings.jsx` (see `WebexMeetingsWidget.propTypes` and `WebexMeetingsWidget.defaultProps`)
+
+### Internal Component Props (WebexMeeting from @webex/components)
+
+These are passed internally by `WebexMeetingsWidget` to the `WebexMeeting` component from `@webex/components`. Consumers do not interact with these directly.
+
+
+| Prop | Type | Description |
+| ---------------------- | ------------- | ------------------------------------------------------- |
+| `meetingID` | `string` | Injected by `withMeeting` HOC from `meetingDestination` |
+| `meetingPasswordOrPin` | `string` | Forwarded from widget prop |
+| `participantName` | `string` | Forwarded from widget prop |
+| `controls` | `Function` | Forwarded from widget prop |
+| `layout` | `string` | Forwarded from widget prop |
+| `logo` | `JSX.Element` | Hard-coded `` SVG |
+| `className` | `string` | Always `'webex-meetings-widget__content'` |
+
+
+The `WebexMeeting` component receives its adapter via `AdapterContext.Provider`, which is set up by the `withAdapter` HOC wrapping the widget.
+
+### Hooks
+
+**Source:** [`@webex/components`](https://github.com/webex/components) → [`src/components/hooks/`](https://github.com/webex/components/tree/master/src/components/hooks)
+
+
+| Hook | Parameters | Returns | Description |
+| ------------------------------------------- | --------------------------------- | ---------------------------------------------- | ------------------------------------------------------------------- |
+| `useMeeting(meetingID)` | `meetingID: string` | Meeting object (see ARCHITECTURE.md for shape) | Subscribes to the adapter's meeting observable |
+| `useMeetingControl(type, meetingID)` | `type: string, meetingID: string` | `[action, display]` (array) | Returns action function and display state for a control |
+| `useMeetingDestination(meetingDestination)` | `meetingDestination: string` | Meeting object | Creates a meeting from destination and subscribes to its observable |
+
+
+### WebexSDKAdapter Methods (top-level adapter)
+
+**Source:** [`@webex/sdk-component-adapter`](https://github.com/webex/sdk-component-adapter) → [`src/WebexSDKAdapter.js`](https://github.com/webex/sdk-component-adapter/blob/master/src/WebexSDKAdapter.js)
+
+
+| Method | Returns | Description |
+| -------------- | --------------- | --------------------------------------------------------------------------------------------------------------- |
+| `connect()` | `Promise` | Calls `sdk.internal.device.register()` → `sdk.internal.mercury.connect()` → `meetingsAdapter.connect()` |
+| `disconnect()` | `Promise` | Calls `meetingsAdapter.disconnect()` → `sdk.internal.mercury.disconnect()` → `sdk.internal.device.unregister()` |
+
+
+### MeetingsSDKAdapter Methods
+
+**Source:** [`@webex/sdk-component-adapter`](https://github.com/webex/sdk-component-adapter) → [`src/MeetingsSDKAdapter.js`](https://github.com/webex/sdk-component-adapter/blob/master/src/MeetingsSDKAdapter.js)
+
+
+| Method | Parameters | Returns | Description |
+| ------------------------------------ | ------------------------------------------------------ | --------------------- | ------------------------------------------------------- |
+| `connect()` | — | `Promise` | Calls `meetings.register()` + `meetings.syncMeetings()` |
+| `disconnect()` | — | `Promise` | Calls `meetings.unregister()` |
+| `createMeeting(destination)` | `destination: string` | `Observable` | Creates a meeting from URL, SIP, or PMR |
+| `joinMeeting(ID, options)` | `ID: string, { password?, name?, hostKey?, captcha? }` | `Promise` | Joins the meeting |
+| `leaveMeeting(ID)` | `ID: string` | `Promise` | Leaves and cleans up the meeting |
+| `handleLocalAudio(ID)` | `ID: string` | `Promise` | Toggles audio mute/unmute |
+| `handleLocalVideo(ID)` | `ID: string` | `Promise` | Toggles video on/off |
+| `handleLocalShare(ID)` | `ID: string` | `Promise` | Toggles screen share on/off |
+| `toggleRoster(ID)` | `ID: string` | `Promise` | Toggles member roster panel (client-side only) |
+| `toggleSettings(ID)` | `ID: string` | `Promise` | Toggles settings modal; applies device changes on close |
+| `switchCamera(ID, cameraID)` | `ID, cameraID: string` | `Promise` | Switches to a different camera device |
+| `switchMicrophone(ID, microphoneID)` | `ID, microphoneID: string` | `Promise` | Switches to a different microphone |
+| `switchSpeaker(ID, speakerID)` | `ID, speakerID: string` | `Promise` | Switches to a different speaker (client-side only) |
+
+
+### Control Action Parameters
+
+**Source:** [`@webex/sdk-component-adapter`](https://github.com/webex/sdk-component-adapter) → [`src/MeetingsSDKAdapter/controls/`](https://github.com/webex/sdk-component-adapter/tree/master/src/MeetingsSDKAdapter/controls)
+
+Each control's `action()` receives a destructured object from the [`useMeetingControl`](https://github.com/webex/components/blob/master/src/components/hooks/useMeetingControl.js) hook and calls the corresponding adapter method internally.
+
+| Control | File | Adapter Method Called |
+| ------------------------- | ---------------------------- | -------------------------------------------- |
+| `AudioControl` | `AudioControl.js` | `handleLocalAudio(meetingID)` |
+| `VideoControl` | `VideoControl.js` | `handleLocalVideo(meetingID)` |
+| `ShareControl` | `ShareControl.js` | `handleLocalShare(meetingID)` |
+| `JoinControl` | `JoinControl.js` | `joinMeeting(meetingID, { password, name })` |
+| `ExitControl` | `ExitControl.js` | `leaveMeeting(meetingID)` |
+| `RosterControl` | `RosterControl.js` | `toggleRoster(meetingID)` |
+| `SettingsControl` | `SettingsControl.js` | `toggleSettings(meetingID)` |
+| `SwitchCameraControl` | `SwitchCameraControl.js` | `switchCamera(meetingID, cameraId)` |
+| `SwitchMicrophoneControl` | `SwitchMicrophoneControl.js` | `switchMicrophone(meetingID, microphoneId)` |
+| `SwitchSpeakerControl` | `SwitchSpeakerControl.js` | `switchSpeaker(meetingID, speakerId)` |
+
+
+### Control IDs for WebexMeetingControlBar
+
+**Source:** Control IDs are registered in [`@webex/sdk-component-adapter`](https://github.com/webex/sdk-component-adapter) → [`src/MeetingsSDKAdapter.js`](https://github.com/webex/sdk-component-adapter/blob/master/src/MeetingsSDKAdapter.js) and rendered by [`WebexMeetingControlBar`](https://github.com/webex/components/tree/master/src/components/WebexMeetingControlBar) from `@webex/components`. The widget passes them via the `controls` prop.
+
+| Control ID | Class | Type | Available |
+| ------------------- | ------------------------- | ----------- | --------------------- |
+| `mute-audio` | `AudioControl` | BUTTON | Pre-join + In-meeting |
+| `mute-video` | `VideoControl` | BUTTON | Pre-join + In-meeting |
+| `share-screen` | `ShareControl` | TOGGLE | In-meeting only |
+| `join-meeting` | `JoinControl` | JOIN | Pre-join only |
+| `leave-meeting` | `ExitControl` | CANCEL | In-meeting only |
+| `member-roster` | `RosterControl` | TOGGLE | In-meeting only |
+| `settings` | `SettingsControl` | BUTTON | Pre-join + In-meeting |
+| `switch-camera` | `SwitchCameraControl` | MULTISELECT | Settings panel |
+| `switch-microphone` | `SwitchMicrophoneControl` | MULTISELECT | Settings panel |
+| `switch-speaker` | `SwitchSpeakerControl` | MULTISELECT | Settings panel |
+
+
+---
+
+## Installation
+The widget declares `react`, `react-dom`, `prop-types`, and `webex` as **peer dependencies**. Consumers must install them alongside the widget at the versions specified in [package.json](../package.json) under `peerDependencies`.
+```bash
+# yarn
+yarn add @webex/widgets react@ react-dom@ prop-types@ webex@
+# npm
+npm install @webex/widgets react@ react-dom@ prop-types@ webex@
+---
+
+## Additional Resources
+
+For detailed architecture, event flows, data structures, and troubleshooting, see [ARCHITECTURE.md](./ARCHITECTURE.md).
+
+---
+
+*Last Updated: 2026-03-09*
\ No newline at end of file
diff --git a/packages/@webex/widgets/ai-docs/ARCHITECTURE.md b/packages/@webex/widgets/ai-docs/ARCHITECTURE.md
new file mode 100644
index 000000000..b80e7a023
--- /dev/null
+++ b/packages/@webex/widgets/ai-docs/ARCHITECTURE.md
@@ -0,0 +1,868 @@
+# Meetings Widget - Architecture
+
+## Component Overview
+
+The Meetings Widget composes three external repositories into an embeddable meeting experience. The widget initializes `webex-js-sdk`, wraps it with `sdk-component-adapter`, and renders `components` repo UI via `AdapterContext`.
+
+### Layer Architecture
+
+```mermaid
+graph TB
+ subgraph "Widget Layer"
+ W[Widget Entry Point]
+ end
+
+ subgraph "UI Layer (components repo)"
+ WM[WebexMeeting]
+ WIM[WebexInterstitialMeeting]
+ WIN[WebexInMeeting]
+ WFH[WebexWaitingForHost]
+ MCB[WebexMeetingControlBar]
+ WLM[WebexLocalMedia]
+ WRM[WebexRemoteMedia]
+ WMR[WebexMemberRoster]
+ WS[WebexSettings]
+ WGA[WebexMeetingGuestAuthentication]
+ WHA[WebexMeetingHostAuthentication]
+ WMI[WebexMeetingInfo]
+ WMA[WebexMediaAccess]
+ end
+
+ subgraph "Adapter Layer (sdk-component-adapter)"
+ ADAPT[MeetingsSDKAdapter]
+ AC[AudioControl]
+ VC[VideoControl]
+ SC[ShareControl]
+ JC[JoinControl]
+ EC[ExitControl]
+ RC[RosterControl]
+ STC[SettingsControl]
+ SCC[SwitchCameraControl]
+ SMC[SwitchMicrophoneControl]
+ SSC[SwitchSpeakerControl]
+ end
+
+ subgraph "SDK Layer (webex-js-sdk)"
+ SDK[Webex Instance]
+ end
+
+ subgraph "Backend"
+ BE[Backend]
+ end
+
+ W -->|creates| SDK
+ W -->|creates| ADAPT
+ W -->|AdapterContext| WM
+ W --> WMA
+ WM --> WIM
+ WM --> WIN
+ WM --> WFH
+ WM --> MCB
+ WM --> WMR
+ WM --> WS
+ WM --> WGA
+ WM --> WHA
+ WIN --> WLM
+ WIN --> WRM
+ WIN --> WMI
+ WIN --> WGA
+ WIN --> WHA
+ WIM --> WMI
+ WFH --> WMI
+ MCB --> AC & VC & SC & JC & EC & RC & STC
+ STC --> SCC & SMC & SSC
+ AC & VC & SC & JC & EC & RC & STC & SCC & SMC & SSC --> ADAPT
+ ADAPT --> SDK
+ SDK --> BE
+
+ style W fill:#e1f5ff,color:#000
+ style ADAPT fill:#fff4e1,color:#000
+ style SDK fill:#ffe1e1,color:#000
+ style BE fill:#f0f0f0,color:#000
+```
+
+
+
+### File Structure
+
+```
+packages/@webex/widgets/
+├── src/
+│ ├── index.js # Package exports
+│ └── widgets/
+│ └── WebexMeetings/
+│ ├── WebexMeetings.jsx # Widget component (main source)
+│ ├── WebexMeetings.css # Widget styles
+│ ├── WebexLogo.jsx # SVG logo component
+│ ├── webex-logo.svg # Logo asset
+│ └── README.md # Component README
+├── tests/
+│ ├── WebexMeetings/
+│ │ └── WebexMeetings.test.jsx # Unit tests
+│ ├── WebexMeeting.e2e.js # E2E tests
+│ ├── pages/
+│ │ ├── MeetingWidget.page.js # Page object for E2E
+│ │ └── Samples.page.js # Samples page object
+│ └── util.js # Test utilities
+├── demo/ # Demo app
+├── ai-docs/
+│ ├── AGENTS.md # Usage, API, examples
+│ └── ARCHITECTURE.md # This file
+├── jest.config.js # Jest configuration
+└── package.json # Package manifest
+```
+
+### Component Table
+
+**Source:** All components below are from `[@webex/components](https://github.com/webex/components)` → `[src/components/](https://github.com/webex/components/tree/master/src/components)`
+
+
+| Component | Folder | Purpose | Render Condition (parent decides) | Internal Data Source (own hooks) |
+| --------------------------------- | ---------------------------------- | --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `WebexMeeting` | `WebexMeeting/` | Master orchestrator — renders correct view based on meeting state | Always (top-level) | `useMeeting(meetingID)` → `ID`, `localAudio`, `localVideo`, `state`, `showRoster`, `settings`, `passwordRequired` |
+| `WebexInterstitialMeeting` | `WebexInterstitialMeeting/` | Pre-join lobby with local media preview | `state === NOT_JOINED` | `useMeeting(meetingID)` → `localVideo` |
+| `WebexInMeeting` | `WebexInMeeting/` | Active meeting view with remote + local media | `state === JOINED` | `useMeeting(meetingID)` → `remoteShare`, `localShare`, `passwordRequired`, `state` |
+| `WebexWaitingForHost` | `WebexWaitingForHost/` | Waiting room when host hasn't started | `else` (not JOINED/NOT_JOINED/LEFT) | `useMeeting(meetingID)` → `ID`; uses `AdapterContext` → `leaveMeeting(ID)` |
+| `WebexMeetingControlBar` | `WebexMeetingControlBar/` | Renders meeting control buttons | Always (when state is truthy and not LEFT) | `useMeeting(meetingID)` → `state`; computes `isActive = state === JOINED` to select controls |
+| `WebexMeetingControl` | `WebexMeetingControl/` | Individual control button | Rendered by `WebexMeetingControlBar` | `useMeetingControl(type, meetingID)` → `[action, display]` |
+| `WebexMeetingInfo` | `WebexMeetingInfo/` | Meeting title and time overlay | Rendered inside `WebexInMeeting`, `WebexInterstitialMeeting`, `WebexWaitingForHost` | `useMeeting(meetingID)` → `ID`, `startTime`, `endTime`, `title` |
+| `WebexMediaAccess` | `WebexMediaAccess/` | Browser media permission prompt (camera/microphone) | `localAudio.permission === 'ASKING'` or `localVideo.permission === 'ASKING'` (in widget) | `useMeeting(meetingID)` → `ID`; uses `AdapterContext` → `ignoreVideoAccessPrompt` / `ignoreAudioAccessPrompt` |
+| `WebexLocalMedia` | `WebexLocalMedia/` | Local camera/screen/preview video | Rendered inside `WebexInMeeting`, `WebexInterstitialMeeting`, `WebexWaitingForHost` | `useMeeting(meetingID)` → `localVideo`, `localShare`, `settings`; also `useMe()` → `ID` |
+| `WebexRemoteMedia` | `WebexRemoteMedia/` | Remote participant video, audio, and share | Rendered inside `WebexInMeeting` | `useMeeting(meetingID)` → `remoteAudio`, `remoteVideo`, `remoteShare`, `error`, `speakerID`; also `useMembers()` |
+| `WebexMemberRoster` | `WebexMemberRoster/` | Participant list panel | `showRoster === true` (in `WebexMeeting`) | `useMembers(destinationID, destinationType)`; `useMe()` → `orgID`. Does NOT use `useMeeting` |
+| `WebexSettings` | `WebexSettings/` | Audio/video device settings modal (tabs) | `settings.visible === true` (in `WebexMeeting`) | None — delegates to `WebexAudioSettings` + `WebexVideoSettings` children |
+| `WebexMeetingGuestAuthentication` | `WebexMeetingGuestAuthentication/` | Guest password entry (rendered in both `WebexMeeting` and `WebexInMeeting`) | `passwordRequired && !meetingPasswordOrPin && state === NOT_JOINED` | `useMeeting(meetingID)` → `ID`, `failureReason`, `invalidPassword`, `requiredCaptcha`; uses `AdapterContext` → `joinMeeting`, `clearInvalidPasswordFlag`, `refreshCaptcha` |
+| `WebexMeetingHostAuthentication` | `WebexMeetingHostAuthentication/` | Host pin entry (rendered in both `WebexMeeting` and `WebexInMeeting`) | User clicks "I'm the host" in guest modal | `useMeeting(meetingID)` → `ID`, `invalidHostKey`; uses `AdapterContext` → `joinMeeting`, `clearInvalidHostKeyFlag` |
+
+
+---
+
+## SDK Integration
+
+**Repos:** [webex-js-sdk](https://github.com/webex/webex-js-sdk) · `[@webex/sdk-component-adapter](https://github.com/webex/sdk-component-adapter)` → `[src/MeetingsSDKAdapter.js](https://github.com/webex/sdk-component-adapter/blob/master/src/MeetingsSDKAdapter.js)`, `[src/MeetingsSDKAdapter/controls/](https://github.com/webex/sdk-component-adapter/tree/master/src/MeetingsSDKAdapter/controls)`
+
+
+| Area | SDK Methods | Adapter Methods | Control Class |
+| ----------------- | ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | ------------------------- |
+| Initialization | `new Webex()`, `device.register()`, `mercury.connect()` | `sdkAdapter.connect()` → calls `meetings.register()` + `syncMeetings()` | — |
+| Meeting creation | `webex.meetings.create(destination)` | `adapter.meetingsAdapter.createMeeting(dest)` | — |
+| Join | `sdkMeeting.verifyPassword()`, `sdkMeeting.join({ pin, moderator, alias })` | `adapter.meetingsAdapter.joinMeeting(ID, options)` | `JoinControl` |
+| Leave | `sdkMeeting.leave()` | `adapter.meetingsAdapter.leaveMeeting(ID)` (also calls `removeMedia`) | `ExitControl` |
+| Mute/Unmute Audio | `sdkMeeting.muteAudio()`, `sdkMeeting.unmuteAudio()` | `adapter.meetingsAdapter.handleLocalAudio(ID)` | `AudioControl` |
+| Mute/Unmute Video | `sdkMeeting.muteVideo()`, `sdkMeeting.unmuteVideo()` | `adapter.meetingsAdapter.handleLocalVideo(ID)` | `VideoControl` |
+| Screen Share | `sdkMeeting.getMediaStreams()`, `sdkMeeting.updateShare()` * | `adapter.meetingsAdapter.handleLocalShare(ID)` | `ShareControl` |
+| Toggle Roster | — (client-side) | `adapter.meetingsAdapter.toggleRoster(ID)` | `RosterControl` |
+| Toggle Settings | `sdkMeeting.updateVideo()`, `sdkMeeting.updateAudio()` (on close, if joined) | `adapter.meetingsAdapter.toggleSettings(ID)` | `SettingsControl` |
+| Switch Camera | `sdkMeeting.getMediaStreams()` * | `adapter.switchCamera(ID, cameraID)` | `SwitchCameraControl` |
+| Switch Microphone | `sdkMeeting.getMediaStreams()` * | `adapter.switchMicrophone(ID, microphoneID)` | `SwitchMicrophoneControl` |
+| Switch Speaker | — (client-side, updates meeting state only) | `adapter.switchSpeaker(ID, speakerID)` | `SwitchSpeakerControl` |
+| Cleanup | `meetings.unregister()`, `mercury.disconnect()`, `device.unregister()` | `sdkAdapter.disconnect()` → calls `meetingsAdapter.disconnect()` then SDK cleanup | — |
+
+
+** `getMediaStreams()` and `updateShare()` are the SDK methods invoked by the adapter source code. In newer SDK versions, equivalent functionality is provided by `media.getUserMedia()`, `addMedia()`, `publishStreams()`, and `updateMedia()`.*
+
+---
+
+## Data Flow
+
+### Outbound (User Action → Backend)
+
+```
+User clicks control button
+ → Component (WebexMeetingControl)
+ → useMeetingControl hook
+ → Control.action({ meetingID })
+ → sdk-component-adapter method
+ → webex-js-sdk meeting method
+ → Backend (REST/WebSocket)
+```
+
+### Inbound (Backend → UI Update)
+
+```
+Backend processes request
+ → WebSocket event delivered to webex-js-sdk
+ → sdk-component-adapter detects change
+ → RxJS BehaviorSubject emits new meeting state
+ → useMeeting hook receives update
+ → Component re-renders
+```
+
+---
+
+## Adapter Meeting Object (from `createMeeting` + runtime updates)
+
+This is the real shape emitted by `adapter.meetingsAdapter.getMeeting(ID)`:
+
+```
+{
+ ID: string
+ title: string
+ state: 'NOT_JOINED' | 'JOINED' | 'LEFT'
+
+ localAudio: {
+ stream: MediaStream | null
+ permission: string | null // 'ASKING' | 'ALLOWED' | 'DISMISSED' | 'DENIED' | 'DISABLED' | 'IGNORED' | 'ERROR' | null
+ muting: boolean | undefined // true = muting in progress, false = unmuting, undefined = idle
+ ignoreMediaAccessPrompt: Function | undefined // callback to dismiss the media access prompt and proceed without audio
+ }
+ localVideo: {
+ stream: MediaStream | null
+ permission: string | null // 'ASKING' | 'ALLOWED' | 'DISMISSED' | 'DENIED' | 'DISABLED' | 'IGNORED' | 'ERROR' | null
+ muting: boolean | undefined
+ error: string | null // e.g. 'Video not supported on iOS 15.1'
+ ignoreMediaAccessPrompt: Function | undefined // callback to dismiss the media access prompt and proceed without video
+ }
+ localShare: {
+ stream: MediaStream | null
+ }
+
+ remoteAudio: MediaStream | null
+ remoteVideo: MediaStream | null
+ remoteShare: MediaStream | null
+
+ disabledLocalAudio: MediaStream | null // stores the stream when audio is muted
+ disabledLocalVideo: MediaStream | null // stores the stream when video is muted
+
+ showRoster: boolean | null
+ settings: {
+ visible: boolean
+ preview: {
+ audio: MediaStream | null
+ video: MediaStream | null
+ }
+ }
+
+ passwordRequired: boolean
+ requiredCaptcha: object
+ remoteShareStream: MediaStream | null // raw remote share stream (may differ from remoteShare timing)
+ remoteSharing: boolean // true when remote participant is sharing
+
+ invalidPassword: boolean // true when entered password was wrong
+ invalidHostKey: boolean // true when entered host key was wrong
+ failureReason: string | undefined // reason from server when password verification fails
+
+ cameraID: string | null
+ microphoneID: string | null
+ speakerID: string | null // '' on creation, null after removeMedia
+}
+```
+
+---
+
+## Event Flows
+
+### 1. SDK Initialization
+
+```mermaid
+sequenceDiagram
+ participant User
+ participant Component as WebexMeeting
+ participant Adapter as sdk-component-adapter
+ participant SDK as webex-js-sdk
+ participant Backend
+
+ User->>Component: Mount widget with accessToken
+ Component->>SDK: new Webex({ credentials: { access_token } })
+ Component->>Adapter: new WebexSDKAdapter(webex)
+ Adapter->>Adapter: Create MeetingsSDKAdapter(webex) with controls
+
+ Component->>Adapter: sdkAdapter.connect()
+ Adapter->>SDK: sdk.internal.device.register()
+ SDK->>Backend: Register device
+ Backend-->>SDK: Device registered
+ Adapter->>SDK: sdk.internal.mercury.connect()
+ SDK->>Backend: Open WebSocket
+ Backend-->>SDK: WebSocket connected
+ Adapter->>SDK: webex.meetings.register() + syncMeetings()
+ SDK-->>Adapter: Meetings ready
+
+ Component->>Component: Render with AdapterContext.Provider
+```
+
+
+
+---
+
+### 2. Meeting Creation & Interstitial
+
+```mermaid
+sequenceDiagram
+ participant User
+ participant Component as WebexMeeting
+ participant Adapter as sdk-component-adapter
+ participant SDK as webex-js-sdk
+ participant Backend
+
+ User->>Component: Provide meeting destination (URL/SIP/PMR)
+ Component->>Adapter: createMeeting(destination)
+ Adapter->>SDK: webex.meetings.create(destination)
+ SDK->>Backend: Resolve meeting info, check active sessions, get user profile
+ Backend-->>SDK: Meeting info (title, sipUri), user profile
+
+ Note over SDK: Meeting object created with state=NOT_JOINED
+
+ SDK-->>Adapter: Meeting object
+ Adapter->>Adapter: Create meeting observable (RxJS)
+ Adapter-->>Component: meetingID
+
+ Component->>Component: Render WebexInterstitialMeeting
+ Component->>Component: Show local media preview
+ Component->>Component: Show controls [mute-audio, mute-video, settings, join-meeting]
+```
+
+
+
+---
+
+### 3. Join Meeting
+
+```mermaid
+sequenceDiagram
+ participant User
+ participant Component as WebexMeetingControlBar
+ participant Adapter as JoinControl
+ participant SDK as webex-js-sdk
+ participant Backend
+
+ User->>Component: Click "Join Meeting" button
+ Component->>Adapter: action({ meetingID })
+ Adapter->>Adapter: joinMeeting(ID, { password, name })
+
+ alt Password Required
+ Adapter->>SDK: sdkMeeting.verifyPassword(password, captcha)
+ SDK->>Backend: Verify password
+ Backend-->>SDK: Verified
+ end
+
+ Adapter->>SDK: sdkMeeting.join({ pin, moderator, alias })
+ SDK->>Backend: Join meeting session
+ Backend-->>SDK: Session joined, media connections ready
+
+ SDK->>SDK: Negotiate media (SDP offer/answer)
+ SDK->>Backend: Send local media description
+ Backend-->>SDK: Media established (audio + video active)
+
+ SDK-->>Adapter: Meeting state updated
+ Adapter->>Adapter: Emit observable { state: JOINED }
+ Adapter-->>Component: Observable emits
+
+ Component->>Component: Transition: WebexInterstitialMeeting → WebexInMeeting
+ Component->>Component: Update controls [mute-audio, mute-video, share-screen, member-roster, settings, leave-meeting]
+```
+
+
+
+---
+
+### 4. Mute / Unmute Audio
+
+```mermaid
+sequenceDiagram
+ participant User
+ participant Component as WebexMeetingControlBar
+ participant Adapter as AudioControl
+ participant SDK as webex-js-sdk
+ participant Backend
+
+ Note over User: Audio is currently UNMUTED
+
+ User->>Component: Click microphone button
+ Component->>Adapter: action({ meetingID })
+ Adapter->>Adapter: handleLocalAudio(ID)
+ Adapter->>Adapter: Set localAudio.muting = true
+ Adapter->>SDK: sdkMeeting.muteAudio()
+ SDK->>Backend: Update media state (audio → receive-only)
+ Backend-->>SDK: Confirmed
+
+ Adapter->>Adapter: Emit { disabledLocalAudio: stream, localAudio.stream: null }
+ Adapter-->>Component: display() emits { icon: microphone-muted, text: Unmute, state: ACTIVE }
+ Component->>Component: Re-render with muted icon
+
+ Note over User: Audio is now MUTED — click again to unmute
+
+ User->>Component: Click microphone button
+ Component->>Adapter: action({ meetingID })
+ Adapter->>Adapter: handleLocalAudio(ID)
+ Adapter->>Adapter: Set localAudio.muting = false
+ Adapter->>SDK: sdkMeeting.unmuteAudio()
+ SDK->>Backend: Update media state (audio → send+receive)
+ Backend-->>SDK: Confirmed
+
+ Adapter->>Adapter: Emit { disabledLocalAudio: null, localAudio.stream: stream }
+ Adapter-->>Component: display() emits { icon: microphone, text: Mute, state: INACTIVE }
+ Component->>Component: Re-render with unmuted icon
+```
+
+
+
+---
+
+### 5. Start / Stop Video
+
+```mermaid
+sequenceDiagram
+ participant User
+ participant Component as WebexMeetingControlBar
+ participant Adapter as VideoControl
+ participant SDK as webex-js-sdk
+ participant Backend
+
+ Note over User: Video is currently ON
+
+ User->>Component: Click camera button
+ Component->>Adapter: action({ meetingID })
+ Adapter->>Adapter: handleLocalVideo(ID)
+ Adapter->>Adapter: Set localVideo.muting = true
+ Adapter->>SDK: sdkMeeting.muteVideo()
+ SDK->>Backend: Update media state (video → receive-only)
+ Backend-->>SDK: Confirmed
+
+ Adapter->>Adapter: Emit { disabledLocalVideo: stream, localVideo.stream: null }
+ Adapter-->>Component: display() emits { icon: camera-muted, text: Start video, state: ACTIVE }
+
+ Note over User: Video is now OFF — click again to start
+
+ User->>Component: Click camera button
+ Component->>Adapter: action({ meetingID })
+ Adapter->>Adapter: handleLocalVideo(ID)
+ Adapter->>Adapter: Set localVideo.muting = false
+ Adapter->>SDK: sdkMeeting.unmuteVideo()
+ SDK->>Backend: Update media state (video → send+receive)
+ Backend-->>SDK: Confirmed
+
+ Adapter->>Adapter: Emit { disabledLocalVideo: null, localVideo.stream: stream }
+ Adapter-->>Component: display() emits { icon: camera, text: Stop video, state: INACTIVE }
+```
+
+
+
+---
+
+### 6. Start / Stop Screen Share
+
+```mermaid
+sequenceDiagram
+ participant User
+ participant Component as WebexMeetingControlBar
+ participant Adapter as ShareControl
+ participant SDK as webex-js-sdk
+ participant Backend
+
+ User->>Component: Click share screen button
+ Component->>Adapter: action({ meetingID })
+ Adapter->>Adapter: handleLocalShare(ID)
+ Adapter->>SDK: sdkMeeting.getMediaStreams({ sendShare: true })
+ SDK->>User: Browser screen picker dialog (getDisplayMedia)
+ User->>SDK: Select screen/window/tab
+ SDK-->>Adapter: [, localShareStream]
+ Adapter->>SDK: sdkMeeting.updateShare({ stream, sendShare: true, receiveShare: true })
+ SDK->>Backend: Update media state (share → send+receive)
+ Backend-->>SDK: Confirmed
+
+ Adapter->>Adapter: Emit { localShare.stream: localShareStream }
+ Adapter-->>Component: display() emits { text: Stop sharing, state: ACTIVE }
+
+ Note over User: Sharing active — click again to stop
+
+ User->>Component: Click stop sharing
+ Component->>Adapter: action({ meetingID })
+ Adapter->>Adapter: handleLocalShare(ID)
+ Adapter->>Adapter: stopStream(localShare.stream)
+ Adapter->>SDK: sdkMeeting.updateShare({ sendShare: false, receiveShare: true })
+ SDK->>Backend: Update media state (share → receive-only)
+ Backend-->>SDK: Confirmed
+
+ Adapter->>Adapter: Emit { localShare.stream: null }
+ Adapter-->>Component: display() emits { text: Start sharing, state: INACTIVE }
+```
+
+
+
+---
+
+### 7. Toggle Member Roster
+
+```mermaid
+sequenceDiagram
+ participant User
+ participant Component as WebexMeeting
+ participant Adapter as RosterControl
+
+ Note over Adapter: Client-side only — no Backend call
+
+ User->>Component: Click roster button
+ Component->>Adapter: action({ meetingID })
+ Adapter->>Adapter: toggleRoster(ID)
+ Adapter->>Adapter: meeting.showRoster = !meeting.showRoster
+ Adapter->>Adapter: Emit observable { showRoster: true }
+ Adapter-->>Component: Observable emits
+ Component->>Component: Render WebexMemberRoster panel
+
+ User->>Component: Click roster button (close)
+ Component->>Adapter: action({ meetingID })
+ Adapter->>Adapter: toggleRoster(ID)
+ Adapter->>Adapter: Emit { showRoster: false }
+ Adapter-->>Component: Observable emits
+ Component->>Component: Remove WebexMemberRoster panel
+```
+
+
+
+---
+
+### 8. Toggle Settings & Switch Camera
+
+```mermaid
+sequenceDiagram
+ participant User
+ participant Component as WebexMeeting
+ participant Adapter as sdk-component-adapter
+ participant SDK as webex-js-sdk
+
+ User->>Component: Click settings button
+ Component->>Adapter: SettingsControl.action({ meetingID })
+ Adapter->>Adapter: toggleSettings(ID)
+ Adapter->>Adapter: Clone current streams to settings.preview
+ Adapter->>Adapter: Emit { settings.visible: true }
+ Adapter-->>Component: Observable emits
+ Component->>Component: Open WebexSettings modal
+
+ Note over User: User selects a different camera
+
+ User->>Component: Select new camera from dropdown
+ Component->>Adapter: SwitchCameraControl.action({ meetingID, cameraId })
+ Adapter->>Adapter: switchCamera(ID, cameraId)
+ Adapter->>SDK: sdkMeeting.getMediaStreams({ sendVideo: true }, { video: { deviceId } })
+ SDK->>SDK: getUserMedia with new deviceId
+ SDK-->>Adapter: New video MediaStream
+ Adapter->>Adapter: Emit { settings.preview.video: newStream, cameraID }
+ Adapter-->>Component: Settings preview re-renders with new camera
+
+ User->>Component: Close settings modal
+ Component->>Adapter: SettingsControl.action({ meetingID })
+ Adapter->>Adapter: toggleSettings(ID)
+ Adapter->>Adapter: Replace meeting streams with preview streams
+
+ alt Meeting is joined
+ Adapter->>SDK: sdkMeeting.updateVideo({ stream, receiveVideo, sendVideo })
+ Adapter->>SDK: sdkMeeting.updateAudio({ stream, receiveAudio, sendAudio })
+ end
+
+ Adapter->>Adapter: Emit { settings.visible: false }
+ Component->>Component: Close modal
+```
+
+
+
+---
+
+### 9. Leave Meeting
+
+```mermaid
+sequenceDiagram
+ participant User
+ participant Component as WebexMeetingControlBar
+ participant Adapter as ExitControl
+ participant SDK as webex-js-sdk
+ participant Backend
+
+ User->>Component: Click leave meeting button
+ Component->>Adapter: action({ meetingID })
+ Adapter->>Adapter: leaveMeeting(ID)
+ Adapter->>Adapter: removeMedia(ID) — stop all local streams
+ Adapter->>SDK: sdkMeeting.leave()
+ SDK->>Backend: Leave session
+ Backend-->>SDK: Confirmed
+
+ SDK-->>Adapter: Meeting state updated
+ Adapter->>Adapter: Emit { state: LEFT }
+ Adapter-->>Component: Observable emits
+
+ Component->>Component: Show "You've successfully left the meeting"
+```
+
+
+
+---
+
+### 10. Guest/Host Authentication
+
+```mermaid
+sequenceDiagram
+ participant User
+ participant Component as WebexMeeting
+ participant Adapter as JoinControl
+ participant SDK as webex-js-sdk
+ participant Backend
+
+ Note over Component: Meeting has passwordRequired=true
+
+ Component->>Component: Detect passwordRequired from observable
+ Component->>Component: Open WebexMeetingGuestAuthentication modal
+
+ User->>Component: Enter password, click "Join as Guest"
+ Component->>Adapter: action({ meetingID })
+ Adapter->>SDK: joinMeeting(ID, { password })
+ SDK->>Backend: Verify password and join
+ Backend-->>SDK: Result
+
+ alt Password Correct
+ SDK-->>Adapter: state → JOINED
+ Adapter-->>Component: Observable emits
+ Component->>Component: Close auth modal, show in-meeting view
+ else Password Incorrect
+ SDK-->>Adapter: Error / invalidPassword flag
+ Adapter-->>Component: Observable emits { invalidPassword: true }
+ Component->>Component: Show error in auth modal
+ end
+
+ Note over User: Alternative: "I'm the host"
+
+ User->>Component: Click "I'm the host"
+ Component->>Component: Switch to WebexMeetingHostAuthentication modal
+ User->>Component: Enter host pin, click "Start Meeting"
+ Component->>Adapter: action({ meetingID })
+ Adapter->>SDK: joinMeeting(ID, { hostKey: hostPin })
+```
+
+
+
+---
+
+### 11. Waiting for Host
+
+```mermaid
+sequenceDiagram
+ participant User
+ participant Component as WebexMeeting
+ participant Adapter as sdk-component-adapter
+ participant SDK as webex-js-sdk
+ participant Backend
+
+ Note over Component: Meeting joined but host not yet present
+
+ Component->>Component: state is not JOINED, NOT_JOINED, or LEFT
+ Component->>Component: Render WebexWaitingForHost
+ Component->>User: Show "Waiting for the host to start the meeting"
+
+ Note over Backend: Host joins the meeting
+
+ Backend-->>SDK: WebSocket event — host joined, meeting started
+ SDK-->>Adapter: Meeting state updated
+ Adapter->>Adapter: Emit { state: JOINED }
+ Adapter-->>Component: Observable emits
+
+ Component->>Component: Transition: WebexWaitingForHost → WebexInMeeting
+```
+
+
+
+---
+
+## Meeting State Machine
+
+```mermaid
+stateDiagram-v2
+ [*] --> NOT_JOINED: SDK + Adapter ready, meeting created
+
+ NOT_JOINED --> JOINED: User joins (JoinControl)
+
+ JOINED --> LEFT: User leaves (ExitControl)
+
+ LEFT --> [*]: Widget unmounts
+```
+
+
+
+*These are the three states emitted by the adapter's meeting observable. The `WebexMeeting` component also handles a falsy state (loading) and an else catch-all (WebexWaitingForHost).*
+
+---
+
+## Control Display States
+
+**Source:** [`@webex/sdk-component-adapter`](https://github.com/webex/sdk-component-adapter) → [`src/MeetingsSDKAdapter/controls/`](https://github.com/webex/sdk-component-adapter/tree/master/src/MeetingsSDKAdapter/controls)
+
+### AudioControl
+
+
+| State | Icon | Text | Tooltip | Control State |
+| ------------ | ------------------ | ------------- | ----------------------- | ------------- |
+| unmuted | `microphone` | Mute | Mute audio | INACTIVE |
+| muted | `microphone-muted` | Unmute | Unmute audio | ACTIVE |
+| muting | `microphone` | Muting... | Muting audio | DISABLED |
+| unmuting | `microphone-muted` | Unmuting... | Unmuting audio | DISABLED |
+| noMicrophone | `microphone-muted` | No microphone | No microphone available | DISABLED |
+
+
+### VideoControl
+
+
+| State | Icon | Text | Tooltip | Control State |
+| -------- | -------------- | ----------- | --------------------- | ------------- |
+| unmuted | `camera` | Stop video | Stop video | INACTIVE |
+| muted | `camera-muted` | Start video | Start video | ACTIVE |
+| muting | `camera` | Stopping... | Stopping video | DISABLED |
+| unmuting | `camera-muted` | Starting... | Starting video | DISABLED |
+| noCamera | `camera-muted` | No camera | No camera available * | DISABLED |
+
+
+ *If `localVideo.error` is set (e.g. `'Video not supported on iOS 15.1'`), the tooltip shows the error string instead of "No camera available".*
+
+### ShareControl
+
+
+| State | Icon | Text | Tooltip | Control State | Type |
+| ------------ | ------------------------------ | ------------- | -------------------------- | ------------- | ------ |
+| inactive | `share-screen-presence-stroke` | Start sharing | Start sharing content | INACTIVE | TOGGLE |
+| active | `share-screen-presence-stroke` | Stop sharing | Stop sharing content | ACTIVE | TOGGLE |
+| notSupported | `share-screen-presence-stroke` | Start sharing | Share screen not supported | DISABLED | TOGGLE |
+
+
+### JoinControl
+
+
+| Text | Tooltip | Hint | Control State | Type |
+| ------------ | ------------ | ------------------------------- | --------------------------------- | ---- |
+| Join meeting | Join meeting | {Muted/Unmuted}, {video on/off} | ACTIVE (if NOT_JOINED) / DISABLED | JOIN |
+
+
+### ExitControl
+
+Renders as a CANCEL type button.
+
+---
+
+## Troubleshooting Guide
+
+### 1. Widget Stuck on Loading
+
+**Symptoms:** Loading state never resolves, no meeting UI appears
+
+**Possible Causes:**
+
+- Invalid or expired access token
+- Network connectivity to backend
+- Device registration failure
+
+**Solutions:**
+
+- Verify the access token is valid and not expired
+- Check network connectivity (browser dev tools network tab)
+- Check browser console for SDK error messages
+
+---
+
+### 2. Audio/Video Not Working After Join
+
+**Symptoms:** Joined meeting but no audio/video, controls show "No camera" or "No microphone"
+
+**Possible Causes:**
+
+- Browser denied `getUserMedia` permissions
+- Media negotiation (SDP/ROAP) failed
+- Media server unreachable
+
+**Solutions:**
+
+- Check browser permission prompts for camera/microphone
+- Verify `getUserMedia` works in browser console
+- Check for errors in SDK logs
+
+---
+
+### 3. Screen Share Not Available
+
+**Symptoms:** Share button disabled, shows "Share screen not supported"
+
+**Possible Causes:**
+
+- Browser doesn't support `getDisplayMedia`
+- Running over HTTP instead of HTTPS
+- `navigator.mediaDevices.getDisplayMedia` is undefined
+
+**Solutions:**
+
+- Verify HTTPS is being used
+- Check browser compatibility
+- `ShareControl` checks `navigator.mediaDevices.getDisplayMedia` availability before enabling
+
+---
+
+### 4. Meeting State Not Updating
+
+**Symptoms:** UI doesn't change after control actions
+
+**Possible Causes:**
+
+- WebSocket connection dropped
+- Observable subscription lost
+- Adapter not emitting updates
+
+**Solutions:**
+
+- Check WebSocket status in network tab
+- Verify the observable subscription is active
+- Look for WebSocket events in the network inspector
+
+---
+
+### 5. Multiple Meeting Instances Created
+
+**Symptoms:** Widget creates duplicate meetings or SDK instances
+
+**Important:** SDK initialization (`new Webex()`, `new WebexSDKAdapter()`) and meeting creation do **not** happen in `WebexMeetingsWidget`'s lifecycle methods. They happen in the `withAdapter` and `withMeeting` HOC wrappers from `@webex/components` (see `src/widgets/WebexMeetings/WebexMeetings.jsx:259-278`). The widget class's own `componentDidMount`/`componentWillUnmount` only manages focus and accessibility wiring — patching those will not fix duplicate initialization.
+
+**Possible Causes:**
+
+- React strict mode causing `withAdapter`/`withMeeting` HOCs to mount twice
+- Consumer re-rendering the widget with a new `accessToken` or `meetingDestination` prop, triggering the adapter factory again
+- Missing cleanup in the HOC layer on unmount
+
+**Solutions:**
+
+- Investigate the `withAdapter` HOC in `@webex/components` — that is where `adapter.connect()`/`adapter.disconnect()` is managed
+- Investigate the `withMeeting` HOC — that is where `createMeeting(destination)` is called
+- Ensure the consumer does not remount `WebexMeetingsWidget` unnecessarily (e.g., by changing a React `key` prop)
+- For React strict mode issues, the fix must be in the HOC layer (in `@webex/components`), not in this widget class
+
+---
+
+### 6. SettingsControl Display State Never Toggles
+
+**Symptoms:** Settings button always shows INACTIVE state even after opening settings
+
+**Root Cause:** This is a known inconsistency in `sdk-component-adapter`. `SettingsControl.display()` reads `showSettings` from the meeting object, but `MeetingsSDKAdapter.toggleSettings()` writes to `settings.visible`. The `showSettings` property is never set by the adapter, so `display()` always sees `undefined` (falsy) and emits INACTIVE.
+
+**Impact:** The settings button icon/text never toggles visually, but the settings modal still opens/closes because `WebexSettings` in `@webex/components` reads `settings.visible` directly.
+
+**Workaround:** None needed for functionality — the modal works. The display state is cosmetic only.
+
+---
+
+### 7. AdapterContext Not Provided
+
+**Symptoms:** Components crash with "Cannot read property of undefined"
+
+**Possible Causes:**
+
+- `AdapterContext.Provider` not wrapping `WebexMeeting`
+- Adapter not yet initialized when components render
+
+**Solutions:**
+
+- Ensure `` wraps all components
+- Wait for adapter to be ready before rendering
+
+---
+
+## Related Documentation
+
+- [Agent Documentation](./AGENTS.md) - Widget usage and API reference
+- [React Patterns](../../../../ai-docs/patterns/react-patterns.md) - Component patterns
+- [TypeScript Patterns](../../../../ai-docs/patterns/typescript-patterns.md) - Type safety and naming conventions
+- [Testing Patterns](../../../../ai-docs/patterns/testing-patterns.md) - Jest, RTL, Playwright guidelines
+
+---
+
+*Last Updated: 2026-03-09*
\ No newline at end of file