Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
439effd
docs(ai-docs): add task refactor migration documentation (old vs new)
akulakum Mar 9, 2026
4a534db
Merge branch 'next' into TASK_REFACTOR_MIGRATION_OLD_VS_NEW
akulakum Mar 10, 2026
45a1bd6
docs(ai-docs): address codex review comments
akulakum Mar 10, 2026
28397c2
docs(ai-docs): address codex second review comments
akulakum Mar 10, 2026
3bd613f
docs(ai-docs): address codex third review comments
akulakum Mar 10, 2026
606dcbf
docs(ai-docs): address codex fourth review comments
akulakum Mar 10, 2026
23c1fab
docs(ai-docs): address codex fifth review comments
akulakum Mar 10, 2026
e4ea922
docs(ai-docs): address codex sixth review comments
akulakum Mar 11, 2026
b35a8c3
Merge branch 'next' into TASK_REFACTOR_MIGRATION_OLD_VS_NEW
akulakum Mar 11, 2026
2313f1a
docs(ai-docs): address codex seventh review comments
akulakum Mar 11, 2026
0024a37
docs(ai-docs): split PR — retain only foundation docs (PR 1/4)
akulakum Mar 11, 2026
abbbaf3
docs(ai-docs): address codex eighth review — fix conference state der…
akulakum Mar 11, 2026
eabc80d
docs(ai-docs): add CC SDK task-refactor branch reference to 001-migra…
akulakum Mar 11, 2026
6e50dc5
docs(ai-docs): address codex ninth review — fix consult mapping, reco…
akulakum Mar 11, 2026
0d08a09
docs(ai-docs): address codex tenth review — add TaskState export, fix…
akulakum Mar 11, 2026
d576676
docs(ai-docs): address codex eleventh review — keep findHoldStatus in…
akulakum Mar 11, 2026
4a28a43
docs(ai-docs): address reviewer feedback — restructure 001 overview, …
akulakum Mar 11, 2026
91dd405
docs(ai-docs): consolidate PR 1 migration docs into single overview
akulakum Mar 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 166 additions & 0 deletions ai-docs/migration/001-migration-overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# Migration Doc 001: Task Refactor Migration Overview

## Purpose

This document set guides the migration of CC Widgets from the **old ad-hoc task state management** to the **new state-machine-driven architecture** in CC SDK (`task-refactor` branch).

---

## Migration Document Index

| # | Document | Scope | Risk | Priority |
|---|----------|-------|------|----------|
| 002 | [002-ui-controls-migration.md](./002-ui-controls-migration.md) | Replace `getControlsVisibility()` with `task.uiControls` | **High** (core UX) | P0 |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to have Priority column here ? How does it help in migration changes ?

| 003 | [003-store-event-wiring-migration.md](./003-store-event-wiring-migration.md) | Refactor store event handlers to leverage state machine events | **Medium** | P1 |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cannot see this file anymore but in this file, change is more about how we don't need to do refreshTaskList anymore so description here should be updated to say somehting like update store event handlers to accommodate task update being taken care in SDK.

Because events being recieved are not state machine events, they are same events but being emitted via state machine

| 004 | [004-call-control-hook-migration.md](./004-call-control-hook-migration.md) | Refactor `useCallControl` hook, timer utils, fix bugs | **High** (largest widget) | P0 |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is fixing bugs and fixing findHoldTimestanp is part of this migration doc ? We should keep it specific to changes coming due to task-refactor

| 005 | [005-incoming-task-migration.md](./005-incoming-task-migration.md) | Refactor `useIncomingTask` for state-machine offer/assign flow | **Low** | P2 |
| 006 | [006-task-list-migration.md](./006-task-list-migration.md) | Refactor `useTaskList` for per-task `uiControls` | **Low** | P2 |
| 007 | [007-outdial-call-migration.md](./007-outdial-call-migration.md) | No changes needed (CC-level, not task-level) | **Low** | P3 |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If no changes needed then why do we have .md file for this ?

| 008 | [008-store-task-utils-migration.md](./008-store-task-utils-migration.md) | Retire or thin out `task-utils.ts`, fix `findHoldTimestamp` dual signatures | **Medium** | P1 |
| 009 | [009-types-and-constants-migration.md](./009-types-and-constants-migration.md) | Align types/constants with SDK, document `UIControlConfig` | **Medium** | P1 |
| 010 | [010-component-layer-migration.md](./010-component-layer-migration.md) | Update `cc-components` to accept new control shape from SDK | **Medium** | P1 |
| 011 | [011-execution-plan.md](./011-execution-plan.md) | Step-by-step spec-first execution plan with 10 milestones | — | — |
| 012 | [012-task-lifecycle-flows-old-vs-new.md](./012-task-lifecycle-flows-old-vs-new.md) | End-to-end task flows (14 scenarios) with old vs new tracing | — | Reference |
| 013 | [013-file-inventory-old-control-references.md](./013-file-inventory-old-control-references.md) | Complete file-by-file inventory of every old control reference | — | Reference |
| 014 | [014-task-code-scan-report.md](./014-task-code-scan-report.md) | Deep code scan findings across both CC SDK and CC Widgets repos | — | Reference |

---

## Key Architectural Shift
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just a suggestion. Migration docs are generally supposed to talking about straightforward changes. Also this documentation is going to be one time use. So adding details here like problems, benefits etc might not be really required. And adding a refernce digram of old arhcitecture and new arhcitecture from widgets perspective along with descriptions of specificallyu those archietctural changes might be better I feel.

For example, Old appraoch section, new approach section talking about the changes are more than enough


### Before (Old Approach)
```
SDK emits 30+ events → Store handlers manually update observables →
Widgets compute UI controls via getControlsVisibility() →
Components receive {isVisible, isEnabled} per control
```

**Problems:**
- Control visibility logic duplicated between SDK and widgets
- Ad-hoc state derivation from raw task data (consult status, hold status, conference flags)
- Fragile: every new state requires changes in widgets, store utils, AND component logic
- No single source of truth for "what state is this task in?"

### After (New Approach)
```
SDK state machine handles all transitions →
SDK computes task.uiControls automatically →
SDK emits task:ui-controls-updated →
Widgets consume task.uiControls directly →
Components receive {isVisible, isEnabled} per control
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might need to update this statement and point towards the new object structure that is being maintained in SDK. Saying that we receive {isVisible, isEnabled} per control is vague considering the change that has been done in SDK

```

**Benefits:**
- Single source of truth: `task.uiControls` from SDK
- Widget code dramatically simplified (remove ~600 lines of control visibility logic)
- Store utils thinned (most consult/conference/hold status checks no longer needed)
- New states automatically handled by SDK, zero widget changes needed
- Parity with Agent Desktop guaranteed by SDK

---

## Repo Paths Reference

### CC Widgets (this repo)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This information might already be present in existing specs that we have. Any particular reason we are adding this here. Also is adding SDK path here,

| Area | Path |
|------|------|
| Task widgets | `packages/contact-center/task/src/` |
| Task hooks | `packages/contact-center/task/src/helper.ts` |
| Task UI utils (OLD) | `packages/contact-center/task/src/Utils/task-util.ts` |
| Task constants | `packages/contact-center/task/src/Utils/constants.ts` |
| Task timer utils | `packages/contact-center/task/src/Utils/timer-utils.ts` |
| Hold timer hook | `packages/contact-center/task/src/Utils/useHoldTimer.ts` |
| Task types | `packages/contact-center/task/src/task.types.ts` |
| Store | `packages/contact-center/store/src/store.ts` |
| Store event wrapper | `packages/contact-center/store/src/storeEventsWrapper.ts` |
| Store task utils (OLD) | `packages/contact-center/store/src/task-utils.ts` |
| Store constants | `packages/contact-center/store/src/constants.ts` |
| CC Components task | `packages/contact-center/cc-components/src/components/task/` |
| CC Components types | `packages/contact-center/cc-components/src/components/task/task.types.ts` |

### CC SDK (task-refactor branch)
| Area | Path |
|------|------|
| State machine | `packages/@webex/contact-center/src/services/task/state-machine/` |
| UI controls computer | `.../state-machine/uiControlsComputer.ts` |
| State machine config | `.../state-machine/TaskStateMachine.ts` |
| Guards | `.../state-machine/guards.ts` |
| Actions | `.../state-machine/actions.ts` |
| Constants (TaskState, TaskEvent) | `.../state-machine/constants.ts` |
| Types | `.../state-machine/types.ts` |
| Task service | `.../task/Task.ts` |
| Task manager | `.../task/TaskManager.ts` |
| Task types | `.../task/types.ts` |
| Sample app | `docs/samples/contact-center/app.js` |

---

## SDK Version Requirements

The CC Widgets migration depends on the CC SDK `task-refactor` branch being merged and released. Key new APIs:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this information relevant for doing the migration ?


| API | Type | Description |
|-----|------|-------------|
| `task.uiControls` | Property (getter) | Pre-computed `TaskUIControls` object |
| `task:ui-controls-updated` | Event | Emitted when any control's visibility/enabled state changes |
| `TaskUIControls` | Type | `{ [controlName]: { isVisible: boolean, isEnabled: boolean } }` |
| `TaskState` | Enum | Explicit task states (IDLE, OFFERED, CONNECTED, HELD, etc.) |

---

## Pre-existing Bugs Found During Analysis
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be part of this doc


These bugs exist in the current codebase and should be fixed during migration:

### 1. Recording Callback Cleanup Mismatch
**File:** `task/src/helper.ts` (useCallControl), lines 634-653

Setup uses `TASK_EVENTS.TASK_RECORDING_PAUSED` / `TASK_EVENTS.TASK_RECORDING_RESUMED`, but cleanup uses `TASK_EVENTS.CONTACT_RECORDING_PAUSED` / `TASK_EVENTS.CONTACT_RECORDING_RESUMED`. Callbacks are never properly removed.

### 2. `findHoldTimestamp` Dual Signatures
Two `findHoldTimestamp` functions with different signatures:
- `store/src/task-utils.ts`: `findHoldTimestamp(task: ITask, mType: string)`
- `task/src/Utils/task-util.ts`: `findHoldTimestamp(interaction: Interaction, mType: string)`

Should be consolidated to one function during migration.

---

## Critical Migration Notes
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any particular reason to highlights these 3 points here ?


### UIControlConfig Is Built by SDK (Not by Widgets)

Widgets do NOT need to provide `UIControlConfig`. The SDK builds it internally from agent profile, `callProcessingDetails`, media type, and voice variant. This means `deviceType`, `featureFlags`, `agentId`, and `conferenceEnabled` **can be removed** from `useCallControlProps` — they are only used for `getControlsVisibility()` which is being eliminated.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve agentId source in useCallControl props migration

This line states agentId can be removed because it is only used for getControlsVisibility, but useCallControl also relies on agentId for participant/timer derivation and this migration set still documents timer helpers with an agentId parameter (Doc 004). If agentId is removed without explicitly documenting the replacement source, call/consult timer behavior will regress during migration.

Useful? React with 👍 / 👎.


### Timer Utils Dependency on `controlVisibility`

`calculateStateTimerData()` and `calculateConsultTimerData()` in `timer-utils.ts` accept `controlVisibility` as a parameter with old control names. These functions must be migrated to accept `TaskUIControls` (new control names).

### `task:wrapup` Race Condition

The SDK sample app uses `setTimeout(..., 0)` before updating UI after `task:wrapup`. Consider adding a similar guard in the hook to avoid control flickering during wrapup transition.

### Sample App Reference Pattern

The CC SDK sample app (`docs/samples/contact-center/app.js`) demonstrates the canonical pattern:

```javascript
task.on('task:ui-controls-updated', () => {
updateCallControlUI(task);
});

function updateCallControlUI(task) {
const uiControls = task.uiControls || {};
applyAllControlsFromUIControls(uiControls);
}

function applyControlState(element, control) {
element.style.display = control?.isVisible ? 'inline-block' : 'none';
element.disabled = !control?.isEnabled;
}
```

---

_Created: 2026-03-09_
_Updated: 2026-03-09 (added deep scan findings, before/after examples, bug reports)_
210 changes: 210 additions & 0 deletions ai-docs/migration/002-ui-controls-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
# Migration Doc 002: UI Controls — `getControlsVisibility()` → `task.uiControls`

## Summary

The largest single change in this migration. CC Widgets currently computes all call control button visibility/enabled states in `task-util.ts::getControlsVisibility()` (~650 lines). The new SDK provides `task.uiControls` as a pre-computed `TaskUIControls` object driven by the state machine, making the widget-side computation redundant.

---

## Old Approach

### Entry Point
**File:** `packages/contact-center/task/src/Utils/task-util.ts`
**Function:** `getControlsVisibility(deviceType, featureFlags, task, agentId, conferenceEnabled, logger)`

### How It Works (Old)
1. Widget calls `getControlsVisibility()` on every render/task update
2. Function inspects raw `task.data.interaction` to derive:
- Media type (telephony, chat, email)
- Device type (browser, agentDN, extension)
- Consult status (via `getConsultStatus()` from store utils)
- Hold status (via `findHoldStatus()` from store utils)
- Conference status (via `task.data.isConferenceInProgress`)
- Participant counts (via `getConferenceParticipantsCount()`)
3. Each control has a dedicated function that returns `{ isVisible, isEnabled }`
4. Result includes both control visibility AND state flags (e.g., `isHeld`, `consultCallHeld`)

### Old Control Names (22 controls + 7 state flags)
| Old Control Name | Type |
|------------------|------|
| `accept` | `Visibility` |
| `decline` | `Visibility` |
| `end` | `Visibility` |
| `muteUnmute` | `Visibility` |
| `holdResume` | `Visibility` |
| `pauseResumeRecording` | `Visibility` |
| `recordingIndicator` | `Visibility` |
| `transfer` | `Visibility` |
| `conference` | `Visibility` |
| `exitConference` | `Visibility` |
| `mergeConference` | `Visibility` |
| `consult` | `Visibility` |
| `endConsult` | `Visibility` |
| `consultTransfer` | `Visibility` |
| `consultTransferConsult` | `Visibility` |
| `mergeConferenceConsult` | `Visibility` |
| `muteUnmuteConsult` | `Visibility` |
| `switchToMainCall` | `Visibility` |
| `switchToConsult` | `Visibility` |
| `wrapup` | `Visibility` |
| **State flags** | |
| `isConferenceInProgress` | `boolean` |
| `isConsultInitiated` | `boolean` |
| `isConsultInitiatedAndAccepted` | `boolean` |
| `isConsultReceived` | `boolean` |
| `isConsultInitiatedOrAccepted` | `boolean` |
| `isHeld` | `boolean` |
| `consultCallHeld` | `boolean` |

---

## New Approach

### Entry Point
**SDK Property:** `task.uiControls` (getter on `ITask`)
**SDK Event:** `task:ui-controls-updated` (emitted when controls change)
**SDK File:** `packages/@webex/contact-center/src/services/task/state-machine/uiControlsComputer.ts`

### How It Works (New)
1. SDK state machine transitions on every event (hold, consult, conference, etc.)
2. After each transition, `computeUIControls(currentState, context)` is called
3. If controls changed (`haveUIControlsChanged()`), emits `task:ui-controls-updated`
4. Widget reads `task.uiControls` — no computation needed on widget side

### New Control Names (17 controls, no state flags)
| New Control Name | Type |
|------------------|------|
| `accept` | `{ isVisible, isEnabled }` |
| `decline` | `{ isVisible, isEnabled }` |
| `hold` | `{ isVisible, isEnabled }` |
| `mute` | `{ isVisible, isEnabled }` |
| `end` | `{ isVisible, isEnabled }` |
| `transfer` | `{ isVisible, isEnabled }` |
| `consult` | `{ isVisible, isEnabled }` |
| `consultTransfer` | `{ isVisible, isEnabled }` |
| `endConsult` | `{ isVisible, isEnabled }` |
| `recording` | `{ isVisible, isEnabled }` |
| `conference` | `{ isVisible, isEnabled }` |
| `wrapup` | `{ isVisible, isEnabled }` |
| `exitConference` | `{ isVisible, isEnabled }` |
| `transferConference` | `{ isVisible, isEnabled }` |
| `mergeToConference` | `{ isVisible, isEnabled }` |
| `switchToMainCall` | `{ isVisible, isEnabled }` |
| `switchToConsult` | `{ isVisible, isEnabled }` |

---

## Old → New Control Name Mapping

| Old Widget Control | New SDK Control | Notes |
|--------------------|-----------------|-------|
| `accept` | `accept` | Same |
| `decline` | `decline` | Same |
| `end` | `end` | Same |
| `muteUnmute` | `mute` | **Renamed** |
| `holdResume` | `hold` | **Renamed** (hold state still togglable) |
| `pauseResumeRecording` | `recording` | **Renamed** |
| `recordingIndicator` | `recording` | **Merged** into `recording` control |
| `transfer` | `transfer` | Same |
| `conference` | `conference` | Same |
| `exitConference` | `exitConference` | Same |
| `mergeConference` | `mergeToConference` | **Renamed** |
| `consult` | `consult` | Same |
| `endConsult` | `endConsult` | Same |
| `consultTransfer` | `consultTransfer` | Same (always hidden in new SDK) |
| `consultTransferConsult` | `transfer` | **Removed** — transfer button handles consult transfer |
| `mergeConferenceConsult` | `mergeToConference` | **Merged** into `mergeToConference` |
| `muteUnmuteConsult` | `mute` | **Merged** into `mute` |
| `switchToMainCall` | `switchToMainCall` | Same |
| `switchToConsult` | `switchToConsult` | Same |
| `wrapup` | `wrapup` | Same |

### Removed State Flags
The following state flags were returned by `getControlsVisibility()` but are no longer needed:

| Old State Flag | Replacement |
|----------------|-------------|
| `isConferenceInProgress` | Derive from `task.uiControls.exitConference.isVisible` if needed |

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Stop deriving conference state from exit-control visibility

isConferenceInProgress cannot be reliably reconstructed from task.uiControls.exitConference.isVisible. In current widget logic, getExitConferenceButtonVisibility only shows that control when isConferenceInProgress && !isConsultInitiatedOrAccepted, so conference sessions with an active consult are still in conference state while the exit-conference control is hidden; following this replacement would misclassify those flows and break conference-state-dependent behavior.

Useful? React with 👍 / 👎.

| `isConsultInitiated` | Derive from `task.uiControls.endConsult.isVisible` if needed |

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve initiated-only consult state mapping

This row maps isConsultInitiated to task.uiControls.endConsult.isVisible, but that control does not represent an initiated-only phase: current logic shows end-consult for broader consult states (getEndConsultButtonVisibility(..., isConsultInitiatedOrAccepted) in packages/contact-center/task/src/Utils/task-util.ts), while initiated-only semantics are still used for behavior like consult timer labeling (calculateConsultTimerData in packages/contact-center/task/src/Utils/timer-utils.ts). Following this mapping will collapse initiated vs accepted consult phases and produce incorrect consult-state UI behavior.

Useful? React with 👍 / 👎.

| `isConsultInitiatedAndAccepted` | No longer needed — SDK handles via controls |
| `isConsultReceived` | No longer needed — SDK handles via controls |
| `isConsultInitiatedOrAccepted` | No longer needed — SDK handles via controls |
| `isHeld` | Derive from `task.uiControls.hold` state or SDK task state |

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Derive held status from task state, not hold control

The table recommends deriving isHeld from task.uiControls.hold, but hold-control enablement is not a reliable held-state signal in consult/conference flows. In current logic, getHoldResumeButtonVisibility disables hold when consult is in progress even when the main call is not held (task/src/Utils/task-util.ts, lines 164–177), so migrating with this mapping will misclassify hold state and drive incorrect hold/resume icon/tooltip behavior.

Useful? React with 👍 / 👎.

| `consultCallHeld` | No longer needed — SDK handles switch controls |

---

## Refactor Pattern (Before/After)

### Before (in `useCallControl` hook)
```typescript
// helper.ts — old approach
const controls = getControlsVisibility(
store.deviceType,
store.featureFlags,
store.currentTask,
store.agentId,
conferenceEnabled,
store.logger
);

// Pass 22 controls + 7 state flags to component
return {
...controls,
// additional hook state
};
```

### After (in `useCallControl` hook)
```typescript
// helper.ts — new approach
const task = store.currentTask;
const uiControls = task?.uiControls ?? getDefaultUIControls();

// Subscribe to UI control updates
useEffect(() => {
if (!task) return;
const handler = () => {
// MobX or setState to trigger re-render
};
task.on('task:ui-controls-updated', handler);
return () => task.off('task:ui-controls-updated', handler);
}, [task]);

// Pass SDK-computed controls directly to component
return {
controls: uiControls,
// additional hook state (timers, mute state, etc.)
};
```

---

## Files to Modify

| File | Action |
|------|--------|
| `task/src/Utils/task-util.ts` | **DELETE** or reduce to `findHoldTimestamp()` only |
| `task/src/helper.ts` (`useCallControl`) | Remove `getControlsVisibility()` call, use `task.uiControls` |
| `task/src/task.types.ts` | Import `TaskUIControls` from SDK, remove old control types |
| `cc-components/src/components/task/task.types.ts` | Align `ControlProps` with new control names |
| `cc-components/src/components/task/CallControl/call-control.tsx` | Update prop names (`holdResume` → `hold`, etc.) |
| `cc-components/src/components/task/CallControl/call-control.utils.ts` | Simplify/remove old control derivation |
| `store/src/task-utils.ts` | Remove `getConsultStatus`, `findHoldStatus` if no longer consumed |

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep findHoldStatus when migrating task utils

This document tells implementers to derive isHeld from findHoldStatus(task, 'mainCall', agentId) earlier in the same mapping, but this checklist item then advises removing findHoldStatus from store/src/task-utils.ts when pruning old helpers. If teams follow the removal guidance before wiring the new held-state path, hold/resume state derivation loses its documented source and migrated call-control UI can show incorrect hold status.

Useful? React with 👍 / 👎.

| All test files for above | Update to test new contract |

---

## Validation Criteria

- [ ] All 17 SDK controls map correctly to widget UI buttons
- [ ] No widget-side computation of control visibility remains
- [ ] `task:ui-controls-updated` event drives re-renders
- [ ] All existing call control scenarios work identically (hold, consult, transfer, conference, wrapup)
- [ ] Digital channel controls work (accept, end, transfer, wrapup only)
- [ ] Default controls shown when no task is active
- [ ] Error boundary still catches failures gracefully

---

_Parent: [001-migration-overview.md](./001-migration-overview.md)_
Loading
Loading