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).
| # | Document | Scope | Risk | Priority |
|---|---|---|---|---|
| 002 | 002-ui-controls-migration.md | Replace getControlsVisibility() with task.uiControls |
High (core UX) | P0 |
| 003 | 003-store-event-wiring-migration.md | Refactor store event handlers to leverage state machine events | Medium | P1 |
| 004 | 004-call-control-hook-migration.md | Refactor useCallControl hook, timer utils, fix bugs |
High (largest widget) | P0 |
| 005 | 005-incoming-task-migration.md | Refactor useIncomingTask for state-machine offer/assign flow |
Low | P2 |
| 006 | 006-task-list-migration.md | Refactor useTaskList for per-task uiControls |
Low | P2 |
| 007 | 007-outdial-call-migration.md | No changes needed (CC-level, not task-level) | Low | P3 |
| 008 | 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 | Align types/constants with SDK, document UIControlConfig |
Medium | P1 |
| 010 | 010-component-layer-migration.md | Update cc-components to accept new control shape from SDK |
Medium | P1 |
| 011 | 011-execution-plan.md | Step-by-step spec-first execution plan with 10 milestones | — | — |
| 012 | 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 | Complete file-by-file inventory of every old control reference | — | Reference |
| 014 | 014-task-code-scan-report.md | Deep code scan findings across both CC SDK and CC Widgets repos | — | Reference |
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?"
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
Benefits:
- Single source of truth:
task.uiControlsfrom 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
| 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 |
| 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 |
Repo: webex/webex-js-sdk (task-refactor) Local path:
/Users/akulakum/Documents/CC_SDK/webex-js-sdk(branch:task-refactor)
| File | Purpose |
|---|---|
uiControlsComputer.ts |
Computes TaskUIControls from TaskState + TaskContext — the single source of truth for all control visibility/enabled states |
constants.ts |
TaskState enum (IDLE, OFFERED, CONNECTED, HELD, CONSULT_INITIATING, CONSULTING, CONF_INITIATING, CONFERENCING, WRAPPING_UP, COMPLETED, TERMINATED, etc.) and TaskEvent enum |
types.ts |
TaskContext, UIControlConfig, TaskStateMachineConfig |
TaskStateMachine.ts |
State machine configuration with transitions, guards, and actions |
actions.ts |
State machine action implementations |
guards.ts |
Transition guard conditions |
../Task.ts |
Task service exposing task.uiControls getter and task:ui-controls-updated event |
../TaskUtils.ts |
Shared utility functions used by uiControlsComputer.ts (e.g., getIsConferenceInProgress, getIsCustomerInCall) |
These decisions in the SDK directly impact how the migration docs should be interpreted:
-
exitConferencevisibility: In the SDK,exitConferenceisVISIBLE_DISABLED(not hidden) during consulting-from-conference. This differs from the old widget logic where it was hidden.exitConference.isVisibleis therefore more reliable in the new SDK for detecting conference state, but consulted agents not in conferencing state still seeDISABLED. -
TaskState.CONSULT_INITIATINGvsCONSULTING: The SDK hasCONSULT_INITIATING(consult requested, async in-progress) andCONSULTING(consult accepted, actively consulting) as distinct states. The old widget constantTASK_STATE_CONSULT('consult') maps toCONSULT_INITIATING, NOTCONSULTING.TaskState.CONSULT_INITIATEDexists in the enum but is marked "NOT IMPLEMENTED". -
Recording control: SDK computes:
recordingInProgress ? VISIBLE_ENABLED : VISIBLE_DISABLED(line 228 ofuiControlsComputer.ts). So:recording.isEnabled = truewhen recording is active (button clickable to pause).recording.isEnabled = falsewhen recording is NOT active (button visible but disabled — nothing to pause/resume). Recording start is handled separately, not via this control'sisEnabledflag. Widget button wiring (disabled: !isEnabled) is correct with this semantic. -
isHeldderivation: The SDK computesisHeldfromserverHold ?? state === TaskState.HELD(line 81 ofuiControlsComputer.ts). Hold control can beVISIBLE_DISABLEDin conference/consulting states without meaning the call is held. Widgets must deriveisHeldfrom task data (findHoldStatus), not fromcontrols.hold.isEnabled. -
UIControlConfigbuilt internally: The SDK buildsUIControlConfigfrom agent profile,callProcessingDetails, media type, and voice variant. Widgets do NOT need to provide it. -
Conference state (
inConference): The SDK computesinConferenceasconferenceActive && (isConferencing || selfInMainCall || consultInitiator)(line 97). This is broader thanisConferencingstate alone, accounting for backend conference flags and consult-from-conference flows.
The CC Widgets migration depends on the CC SDK task-refactor branch being merged and released. Key new APIs:
| 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.) |
As of the
task-refactorbranch snapshot reviewed, the items below are properly exported from their individual source files within the SDK but are not yet re-exported from the package-level entry point (src/index.ts). This means widgets cannotimport { ... } from '@webex/contact-center'for these items until the SDK team adds them.A Jira ticket is being created to track adding these missing exports to the SDK
src/index.tsbefore the widget migration begins.
The uiControls getter is defined on the concrete Task class and works at runtime on all task instances (Voice, Digital, WebRTC). However, it is not yet declared on the ITask interface — only on IDigital. Since widgets import ITask, TypeScript won't recognize task.uiControls until the interface is updated.
SDK change: Add uiControls: TaskUIControls to ITask interface in services/task/types.ts.
TaskUIControls is exported from services/task/types.ts but not re-exported from src/index.ts. Similarly, TaskUIControlState (the { isVisible, isEnabled } shape) is a local type — should be exported if widgets need it for prop typing.
SDK change: Add to the "Task related types" export block in src/index.ts:
export type { TaskUIControls, TaskUIControlState } from './services/task/types';getDefaultUIControls() is exported from uiControlsComputer.ts and the state-machine index.ts, but not from src/index.ts. Widgets need it as a fallback: task?.uiControls ?? getDefaultUIControls().
SDK change: Add to src/index.ts:
export { getDefaultUIControls } from './services/task/state-machine/uiControlsComputer';TaskState is exported from the state-machine internal module but not from the package entry point. Widgets need it for consult timer labeling — calculateConsultTimerData must distinguish CONSULT_INITIATING (consult requested) from CONSULTING (consult accepted) for correct timer labels.
SDK change: Add to src/index.ts:
export { TaskState } from './services/task/state-machine/constants';These task subtype interfaces are defined but not re-exported. Widgets may need them for type narrowing (e.g., to access holdResume() on voice tasks).
SDK change: Add to src/index.ts:
export type { IVoice, IDigital, IWebRTC } from './services/task/types';The base Task class defines hold() and resume() that throw unsupportedMethodError. Voice tasks override both to delegate to holdResume() — a single toggle. The ITask interface exposes hold(mediaResourceId?) and resume(mediaResourceId?), but voice tasks actually use holdResume() internally (from IVoice).
Widget impact: Widgets calling task.hold() / task.resume() will work correctly on voice tasks (they delegate to holdResume). No widget change needed unless widgets want to call holdResume() directly — in which case they need IVoice typing (covered in item 4 above).
These bugs exist in the current codebase and should be fixed during migration:
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.
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.
Widget store.types.ts declares a local TASK_EVENTS enum (line 210: TODO: remove this once cc sdk exports this enum) with 5 events using CC-level naming that differ from SDK task-level naming:
AGENT_WRAPPEDUP = 'AgentWrappedUp'→ SDK:TASK_WRAPPEDUP = 'task:wrappedup'AGENT_CONSULT_CREATED = 'AgentConsultCreated'→ SDK:TASK_CONSULT_CREATED = 'task:consultCreated'AGENT_OFFER_CONTACT = 'AgentOfferContact'→ SDK:TASK_OFFER_CONTACT = 'task:offerContact'CONTACT_RECORDING_PAUSED = 'ContactRecordingPaused'→ SDK:TASK_RECORDING_PAUSED = 'task:recordingPaused'CONTACT_RECORDING_RESUMED = 'ContactRecordingResumed'→ SDK:TASK_RECORDING_RESUMED = 'task:recordingResumed'
See 009-types-and-constants-migration.md § Event Constants for the complete mapping.
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, and conferenceEnabled can be removed from useCallControlProps — they are only used for getControlsVisibility() which is being eliminated. Note: agentId must be retained because it is also used by timer utilities (calculateStateTimerData, calculateConsultTimerData) to look up the agent's participant record from interaction.participants.
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).
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.
The CC SDK sample app (docs/samples/contact-center/app.js) demonstrates the canonical pattern:
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) Updated: 2026-03-11 (SDK export gaps, holdResume, event name mismatches — from deep SDK comparison)