diff --git a/packages/contact-center/ai-docs/migration/component-layer-migration.md b/packages/contact-center/ai-docs/migration/component-layer-migration.md
new file mode 100644
index 000000000..5083a2313
--- /dev/null
+++ b/packages/contact-center/ai-docs/migration/component-layer-migration.md
@@ -0,0 +1,605 @@
+# Component Layer (`cc-components`) Migration
+
+## Summary
+
+The `cc-components` package contains the presentational React components for task widgets. These components receive control visibility as props. The prop interface must be updated to match the new `TaskUIControls` shape from SDK (renamed controls, merged controls, removed state flags).
+
+### Source of truth — the task object (`ITask`)
+
+After the task-refactor, **everything comes from the SDK task object** (`ITask`). Widgets should not derive state with helper functions like `getControlsVisibility()` or `findHoldStatus()`, and should have zero awareness of the SDK's internal state machine.
+
+- **Control visibility and enablement:** `task.uiControls` — a property on the `ITask` object. Each of the 17 controls has `{ isVisible: boolean, isEnabled: boolean }`. The widget hook reads `store.currentTask.uiControls` and passes it to the component. Do not derive from `deviceType`, `featureFlags`, `conferenceEnabled`, or the legacy `getControlsVisibility()`.
+- **Hold state (`isHeld`, `consultCallHeld`):** Provided by the task object. Do not use the legacy `findHoldStatus()` helper or `controls.hold.isEnabled` (those are action flags, not hold state).
+- **Conference state (`isConferenceInProgress`):** Provided by the task object (e.g. `task.data.isConferenceInProgress`). Do not use `controls.exitConference.isVisible` as sole source — it can be false when consult is active even if conference is in progress.
+
+---
+
+## ControlVisibility Interface — Delete and Replace
+
+**File:** `cc-components/src/components/task/task.types.ts`
+
+The old `ControlVisibility` interface (22 controls + 7 state flags) must be replaced with `TaskUIControls` from the SDK. All new control values come from `task.uiControls` — a property on the `ITask` object provided by the SDK task-refactor branch.
+
+```typescript
+// OLD — DELETE this interface
+export interface ControlVisibility {
+ accept: Visibility; // → task.uiControls.accept (same name)
+ decline: Visibility; // → task.uiControls.decline (same name)
+ end: Visibility; // → task.uiControls.end (same name)
+ muteUnmute: Visibility; // → task.uiControls.mute (renamed)
+ muteUnmuteConsult: Visibility; // → REMOVE — use task.uiControls.mute (single mute control covers both main and consult)
+ holdResume: Visibility; // → task.uiControls.hold (renamed)
+ consult: Visibility; // → task.uiControls.consult (same name)
+ transfer: Visibility; // → task.uiControls.transfer (same name)
+ conference: Visibility; // → task.uiControls.conference; SDK also has task.uiControls.mergeToConference — use mergeToConference for Merge action
+ wrapup: Visibility; // → task.uiControls.wrapup (same name)
+ pauseResumeRecording: Visibility; // → task.uiControls.recording (renamed)
+ endConsult: Visibility; // → task.uiControls.endConsult (same name)
+ recordingIndicator: Visibility; // → REMOVE — merged into task.uiControls.recording (use recording.isVisible for badge, recording.isEnabled for toggle)
+ exitConference: Visibility; // → task.uiControls.exitConference (same name)
+ mergeConference: Visibility; // → task.uiControls.mergeToConference (renamed)
+ consultTransfer: Visibility; // → task.uiControls.consultTransfer — NOTE: always hidden in new SDK; use task.uiControls.transfer or task.uiControls.transferConference instead
+ mergeConferenceConsult: Visibility; // → REMOVE — use task.uiControls.mergeToConference (single control covers both main and consult merge)
+ consultTransferConsult: Visibility; // → REMOVE — use task.uiControls.transfer for consult transfer, task.uiControls.transferConference for conference transfer
+ switchToMainCall: Visibility; // → task.uiControls.switchToMainCall (same name)
+ switchToConsult: Visibility; // → task.uiControls.switchToConsult (same name)
+ isConferenceInProgress: boolean; // → use `task.data.isConferenceInProgress` (SDK provides this directly); do NOT use controls.exitConference.isVisible as sole source — it can be false when consult is active even if conference is in progress
+ isConsultInitiated: boolean; // → Do NOT use endConsult.isVisible as "initiated only"; it covers both initiated and accepted. Use `task.data.consultStatus` if you need that distinction (e.g. `consultInitiated` vs `consultAccepted`).
+ isConsultInitiatedAndAccepted: boolean; // → REMOVE
+ isConsultReceived: boolean; // → REMOVE
+ isConsultInitiatedOrAccepted: boolean; // → REMOVE
+ isHeld: boolean; // → get from task object (SDK provides hold state). Do NOT use controls.hold.isEnabled (that is an action flag, not hold state).
+ consultCallHeld: boolean; // → get from task object. Do NOT use controls.switchToConsult.isVisible (that is button visibility, not hold state).
+}
+
+// NEW — import via store to preserve layering (cc-components → store → SDK). Store re-exports TaskUIControls from SDK.
+import type { TaskUIControls } from '@webex/cc-store';
+```
+
+---
+
+## Components to Update
+
+### CallControlComponent
+**File:** `packages/contact-center/cc-components/src/components/task/CallControl/call-control.tsx`
+
+#### Old Prop Names → New Prop Names
+
+| Old Prop | New Prop | Change |
+|----------|----------|--------|
+| `holdResume` | `hold` | **Rename** |
+| `muteUnmute` | `mute` | **Rename** |
+| `pauseResumeRecording` | `recording` | **Rename** — toggle button (pause/resume) |
+| `recordingIndicator` | `recording` | **Same SDK control** — widget must preserve separate recording status badge UI. Use `recording.isVisible` for badge, `recording.isEnabled` for toggle |
+| `mergeConference` | `mergeToConference` | **Rename**. SDK also has a separate `conference` control; both are visible during consulting when initiator, agent joined, and not at max participants. Use `mergeToConference` for the Merge action; `conference` is a semantic alias for the same merge-from-consult flow. |
+| `consultTransferConsult` | `transfer` / `transferConference` | **Split** — use `transfer` for consult transfer, `transferConference` for conference transfer |
+| `mergeConferenceConsult` | — | **Remove** (use `mergeToConference`) |
+| `muteUnmuteConsult` | — | **Remove** (use `mute`) |
+| `isConferenceInProgress` | — | **Remove** (use `task.data.isConferenceInProgress` directly; do not use `controls.exitConference.isVisible` as sole source) |
+| `isConsultInitiated` | — | **Remove** (if needed, use `task.data.consultStatus` for consult phase distinction) |
+| `isConsultInitiatedAndAccepted` | — | **Remove** |
+| `isConsultReceived` | — | **Remove** |
+| `isConsultInitiatedOrAccepted` | — | **Remove** |
+| `isHeld` | `isHeld` | **Retain** — get from the task object (SDK provides hold state). Do NOT derive from `controls.hold.isEnabled`. |
+| `consultCallHeld` | — | **Remove** (get from the task object if needed for display) |
+
+#### Proposed New Interface
+
+```typescript
+interface CallControlComponentProps {
+ controls: TaskUIControls; // All 17 controls from task.uiControls
+ // Hold state from the task object (SDK provides this). Do NOT use findHoldStatus() or controls.hold.isEnabled.
+ isHeld: boolean;
+ isMuted: boolean;
+ isRecording: boolean;
+ holdTime: number;
+ secondsUntilAutoWrapup: number;
+ buddyAgents: BuddyDetails[]; // Use exported type from @webex/cc-store or task.types (not a generic Agent type)
+ consultAgentName: string;
+ // Actions. onToggleHold(hold) — pass intended hold state (true = hold, false = resume); matches toggleHold(hold: boolean) in task.types.
+ onToggleHold: (hold: boolean) => void;
+ onToggleMute: () => void;
+ onToggleRecording: () => void;
+ onEndCall: () => void;
+ onWrapupCall: (reason: string, auxCodeId: string) => void; // Invoked from wrap-up UI on submit
+ onTransferCall: (payload: TransferPayLoad) => void; // Invoked from transfer popover on submit
+ onConsultCall: (payload: ConsultPayload) => void; // Invoked from consult popover on submit
+ onEndConsultCall: () => void;
+ onConsultTransfer: () => void;
+ onConsultConference: () => void;
+ onExitConference: () => void;
+ onSwitchToConsult: () => void;
+ onSwitchToMainCall: () => void;
+ onCancelAutoWrapup: () => void;
+}
+```
+
+### CallControlConsult
+**File:** `packages/contact-center/cc-components/src/components/task/CallControl/CallControlCustom/call-control-consult.tsx`
+
+- Update to use `controls.endConsult`, `controls.mergeToConference`, `controls.switchToMainCall`, `controls.switchToConsult`
+- Remove separate `consultTransferConsult`, `mergeConferenceConsult`, `muteUnmuteConsult` props
+
+### IncomingTaskComponent
+**File:** `packages/contact-center/cc-components/src/components/task/IncomingTask/incoming-task.tsx`
+
+- Accept: `controls.accept.isVisible` / `controls.accept.isEnabled`
+- Decline: `controls.decline.isVisible` / `controls.decline.isEnabled`
+- Minimal changes — shape is compatible
+
+### TaskListComponent
+**File:** `packages/contact-center/cc-components/src/components/task/TaskList/task-list.tsx`
+
+- Per-task accept/decline: use `task.uiControls.accept` / `task.uiControls.decline`
+- Task status display: if consult status is needed for labels, use `task.data.consultStatus` (SDK provides directly)
+
+### OutdialCallComponent
+**File:** `packages/contact-center/cc-components/src/components/task/OutdialCall/outdial-call.tsx`
+
+- **No changes needed** — OutdialCall does not use task controls
+
+---
+
+## Full Before/After: CallControlComponent
+
+### Before
+```tsx
+// call-control.tsx — old approach
+const CallControlComponent = ({
+ // 22 individual control props
+ accept, decline, end, muteUnmute, holdResume,
+ pauseResumeRecording, recordingIndicator,
+ transfer, conference, exitConference, mergeConference,
+ consult, endConsult, consultTransfer, consultTransferConsult,
+ mergeConferenceConsult, muteUnmuteConsult,
+ switchToMainCall, switchToConsult, wrapup,
+ // 7 state flags
+ isConferenceInProgress, isConsultInitiated,
+ isConsultInitiatedAndAccepted, isConsultReceived,
+ isConsultInitiatedOrAccepted, isHeld, consultCallHeld,
+ // Actions and hook state
+ isMuted, isRecording, holdTime, onToggleHold, onToggleMute, ...
+}) => {
+ return (
+
+ );
+};
+```
+
+### After
+```tsx
+// call-control.tsx — new approach
+const CallControlComponent = ({
+ controls, // TaskUIControls — all 17 controls from task.uiControls
+ isHeld, // From task object (SDK provides hold state)
+ isMuted, isRecording, holdTime,
+ onToggleHold, onToggleMute, onEndCall, onEndConsultCall,
+ onConsultTransfer, onConsultConference, onExitConference,
+ onSwitchToMainCall, onSwitchToConsult, ...
+}: CallControlComponentProps) => {
+ // Implement openTransferPopover / openConsultPopover / openWrapupPopover (e.g. set state to show popover); popover on submit calls onTransferCall(payload) / onConsultCall(payload) / onWrapupCall(reason, auxCodeId).
+ // Derive display-only flags from controls (replaces old state flag props)
+ const isConsulting = controls.endConsult.isVisible;
+ // Get from task object; do not use controls.exitConference.isVisible as sole source
+ const isConferencing = currentTask.data.isConferenceInProgress;
+
+ // isHeld comes from the task object (SDK provides hold state). Do NOT use controls.hold.isEnabled for toggle — that is an action flag, not hold state.
+ return (
+