Skip to content

fix(task-refactor)migrate widgets to SDK state-machine-driven uiControls#668

Open
akulakum wants to merge 4 commits intowebex:task-refactorfrom
akulakum:TASK_REFACTOR_IMPLEMENTATION
Open

fix(task-refactor)migrate widgets to SDK state-machine-driven uiControls#668
akulakum wants to merge 4 commits intowebex:task-refactorfrom
akulakum:TASK_REFACTOR_IMPLEMENTATION

Conversation

@akulakum
Copy link
Copy Markdown
Contributor

@akulakum akulakum commented Apr 7, 2026

COMPLETES #https://jira-eng-sjc12.cisco.com/jira/browse/CAI-7779

This pull request addresses

The CC Widgets currently compute button visibility and enablement on the widget side using a complex combination of deviceType, featureFlags, conferenceEnabled, task state flags, and ~22 visibility functions in getControlsVisibility(). This approach duplicates logic that the SDK now handles internally through its state-machine-driven task.uiControls — a property on the ITask object where each of the 17 controls has { isVisible: boolean, isEnabled: boolean } computed from TaskState and TaskContext. This PR migrates all task widgets to consume task.uiControls as the single source of truth, eliminating widget-side control computation and aligning with the SDK's new architecture.

by making the following changes

Core migration — SDK uiControls as single source of truth:

  • Replaced the legacy getControlsVisibility() / ControlVisibility pattern in useCallControl hook with direct consumption of task.uiControls (type: TaskUIControls)
  • All button isVisible and isEnabled properties in buildCallControlButtons(), createConsultButtons(), and extractIncomingTaskData() / extractTaskListItemData() now read from controls..isVisible / controls..isEnabled
  • Subscribe to TASK_EVENTS.TASK_UI_CONTROLS_UPDATED for reactive re-renders when SDK recomputes controls after state transitions

Props removed (SDK handles internally):

  • deviceType — SDK uses UIControlConfig internally
  • featureFlags — SDK gates via config.isEndTaskEnabled, config.isEndConsultEnabled, config.isRecordingEnabled

Props restored (application-level overrides, not feature flags):

  • conferenceEnabled — application-level configuration from consumer apps (e.g., App.tsx) that controls whether conference-related buttons (merge, exit conference) are available. Applied directly at button builder level: isVisible: conferenceEnabled && (controls?.mergeToConference?.isVisible ?? false). Defaults to true. Exposed as r2wc boolean prop on WebCallControl and WebCallControlCAD
  • isDeclineButtonEnabled — store-level flag for auto-answer scenarios that overrides SDK's decline.isEnabled. Threaded through IncomingTask and TaskList component hierarchies

Hold state handling:

  • Introduced isHeld state in useCallControl hook, initialized from isInteractionOnHold(currentTask) and updated by TASK_HOLD / TASK_RESUME event callbacks
  • buildCallControlButtons() uses isHeld to toggle hold/resume icon and tooltip (not controls.hold.isEnabled, which is an action flag)

Transfer button mapping fix:

  • transferConsult button now correctly maps to controls.consultTransfer (not controls.transfer) to prevent duplicate "Transfer" buttons when SDK sets transfer: visible and consultTransfer: hidden

Type and Web Component updates:

  • Updated ControlProps, CallControlComponentProps, CallControlConsultComponentsProps, IncomingTaskComponentProps, TaskListComponentProps in task.types.ts
  • Updated CallControlProps and useCallControlProps in task/src/task.types.ts
  • Added conferenceEnabled: 'boolean' to r2wc prop definitions in wc.ts

Migration documentation:

  • Updated call-control-hook-migration.md, component-layer-migration.md, and store-task-utils-migration.md with fix logs for all restored props, corrected "Props removed" tables, and documented design decisions

Change Type

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Tooling change
  • Internal code refactor

The following scenarios were tested

  • The testing is done with the amplify link
    < ENUMERATE TESTS PERFORMED, WHETHER MANUAL OR AUTOMATED >

The GAI Coding Policy And Copyright Annotation Best Practices

  • GAI was not used (or, no additional notation is required)
  • Code was generated entirely by GAI
  • GAI was used to create a draft that was subsequently customized or modified
  • Coder created a draft manually that was non-substantively modified by GAI (e.g., refactoring was performed by GAI on manually written code)
  • Tool used for AI assistance (GitHub Copilot / Other - specify)
    • Github Copilot
    • Other - Please Specify
  • This PR is related to
    • Feature
    • Defect fix
    • Tech Debt
    • Automation

Checklist before merging

  • I have not skipped any automated checks
  • All existing and new tests passed
  • I have updated the testing document
  • I have tested the functionality with amplify link

Make sure to have followed the contributing guidelines before submitting.

@akulakum akulakum requested a review from a team as a code owner April 7, 2026 16:21
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2227d693dc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +588 to +590
if (lastWrapupAuxCodeIdRef.current) {
const wrapUpReason = store.wrapupCodes.find((code) => code.id === lastWrapupAuxCodeIdRef.current)?.name;
if (onWrapUp) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Restore onWrapUp callback for SDK-driven wrapups

wrapupCallCallback now emits onWrapUp only when lastWrapupAuxCodeIdRef.current is present, but that ref is only set in wrapupCall(). As a result, wrapups triggered outside this path (for example auto-wrapup or other SDK-driven/task-synced wrapup events) will fire TASK_WRAPPEDUP but never notify consumers via onWrapUp, which breaks callback-based integrations such as analytics or workflow hooks.

Useful? React with 👍 / 👎.

</div>
</>
)}
{!controls?.wrapup?.isVisible && isInteractionOnHold(currentTask) && (
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep consult-state gating for the CAD hold indicator

The hold badge now depends only on isInteractionOnHold(currentTask), which becomes true when either the main leg or consult leg is held. During consult/conference flows one leg is often intentionally held while the agent is active on the other, so this change can incorrectly show an ON HOLD status. The previous logic gated this by consult-state flags, and removing that gating regresses status accuracy.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c63805a45b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +932 to +935
store.lastConsultDestination ?? {
to: currentTask.data.destAgentId,
destinationType: 'agent' as DestinationType,
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve consult destination for consult transfer

consultTransfer() now falls back to currentTask.transfer({ to: currentTask.data.destAgentId, destinationType: 'agent' }) when store.lastConsultDestination is missing. That store field is only set in consultCall(), so hydrated/multi-session consults (or any flow where the consult was not initiated in this hook) can lose the original destination type. In those cases, queue/EP consult transfers can be sent as agent transfers with an incorrect or empty destination, causing transfer failure or routing to the wrong target.

Useful? React with 👍 / 👎.

Comment on lines +110 to +111
// Derive consultCallHeld from controls: switchToConsult.isVisible means consult call is held
const consultCallHeld = controls.switchToConsult?.isVisible ?? false;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Derive consult hold timer from actual hold state

This computes consultCallHeld from controls.switchToConsult.isVisible, which is a UI visibility signal rather than the consult leg's hold state. If the button is hidden for reasons other than hold status, the timer logic will mislabel a held consult as active consulting and use the wrong timestamp. The timer should be driven by task/media hold state, not button visibility.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 24267feeb5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

this.setState({reset: true});
// Ensure agent state is set to Available (auxCodeId '0') when no tasks remain
// The backend should send AGENT_STATE_CHANGE, but in test environments it may not
this.setCurrentState('0');
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Remove forced Available state on empty task list

Setting currentState to '0' inside refreshTaskList() introduces a side effect beyond UI cleanup: when UserState is mounted, its useEffect reacts to currentState changes and calls cc.setAgentState(...), so ending the last task can unexpectedly push the agent to Available even if the backend intended a different post-call state. This regression was introduced by the new unconditional this.setCurrentState('0') and can cause incorrect agent-state transitions in production flows.

Useful? React with 👍 / 👎.

isTelephony,
logger
);
const isConsulting = (controls?.consult?.endConsult?.isVisible || controls?.main?.endConsult?.isVisible) ?? false;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Derive consult mode without relying on end-consult visibility

Using endConsult.isVisible as the consult-state detector is not reliable: consult can be active while end-consult is hidden (for example when end-consult is disabled by configuration), so this check can treat an active consult as non-consulting. In that case consult-specific filtering is skipped here, and the same predicate also hides the CAD consult panel, leaving incorrect controls during an ongoing consult session.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 40a5f9f957

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

return buttons;
}

return buttons.filter((button) => !['hold', 'consult', 'transfer', 'record'].includes(button.id));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Stop filtering recording control during consult

filterButtonsForConsultation now always removes the record button whenever a consult is active, which bypasses the SDK-provided controls.main.recording visibility/enablement and prevents agents from pausing/resuming recording during consult flows where recording is still allowed. This is a regression from the prior behavior (which only filtered hold/consult) and can break recording-control workflows in consult scenarios.

Useful? React with 👍 / 👎.

Comment on lines +255 to +258
disabled: !(mainCtrl?.transfer?.isEnabled ?? false),
isVisible:
(mainCtrl?.transfer?.isVisible ?? false) &&
((controls?.consult?.endConsult?.isVisible || controls?.main?.endConsult?.isVisible) ?? false) &&
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Use conference transfer control when invoking transferConference

The transferConsult button is gated only by mainCtrl.transfer (isEnabled/isVisible), but the action handler can switch to currentTask.transferConference() in conference flows. When SDK state exposes conference-transfer availability separately from regular transfer, this mapping can hide or disable the button even though conference transfer is the intended action, leaving no way to trigger that path from the UI.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant