Stop Late Chat Loads Reopening a Closed Chooser

The shared chat launcher in our website work has two jobs that look similar until the network gets slow. It needs to load a third-party chat widget, and it needs to open that widget when the visitor still wants it.
The October implementation keeps those jobs separate. The evidence here is the component, its tests and the recorded browser checks. Those checks describe opening and closing the widget; they don’t establish conversation or conversion results. The useful lesson is how the launcher handles a request that finishes after the person has already moved on.
Load the provider after someone chooses it
The launcher first opens a chooser with website chat and an external messaging option. Opening that chooser doesn’t load the website-chat provider. The script is requested only when the website-chat option is selected.
The loader keeps one promise at module level. Repeated calls reuse that promise and the provider connection rather than appending another script every time someone reopens the chooser. A component test checks that the chooser itself doesn’t call the loader, while a loader test checks that repeated loads return the same promise.
That makes the application responsible for one shared entry point. When the provider is minimized, its native launcher is hidden and the shared button returns. Without that coordination, two visible launchers could compete for the same small corner of the page.
Retire the intention to open
Imagine selecting website chat, pressing Escape while it loads, and then reading another section. A late successful load shouldn’t pull the chat panel open over that new task. Finishing a download doesn’t mean the earlier request still owns the screen.
The component keeps a request identifier. Starting a request records the current identifier. Dismissal, timeout and other cancellation paths advance it. When the loader resolves, the continuation compares its saved identifier with the current one before calling the provider’s show and maximize methods.
Choosing the other messaging channel also cancels the pending open. So does moving focus outside the chooser. These aren’t just ways to hide a panel; they invalidate the older request’s right to open one later.
This is UI cancellation. It doesn’t claim to stop every network byte or undo code that a third-party script has already executed. Keeping that limitation clear makes the implementation easier to reason about.
Make a stalled loader retryable
A promise that never settles can leave every later attempt waiting on the same unfinished work. The loader therefore has its own timeout. It removes the stalled script, resets the shared promise and rejects the load so a later choice can try again.
In this change, the loader timeout is fourteen seconds and the chooser’s timeout is fifteen. The loader is meant to release its state before the chooser reaches its own limit. Those are implementation choices for this component, not recommended universal timeout values.
A normal script error follows the same reset path. Once the promise is rejected and cleared, the next request can create a new script. The tests cover both a network-error event and a stalled load.
Keep the other channel available
The chooser keeps the external messaging link available during loading and after an error. An error message offers a retry instead of leaving the person with a spinner that has no end. When website chat isn’t configured, the chooser still offers the remaining channel.
This is also where status wording matters. The interface describes loading, failure and the available next action. It doesn’t tell someone their message failed when they haven’t reached a conversation yet.
The analytics follow the same distinction. Selecting a channel emits a channel-selection event. It doesn’t establish that a conversation happened, that a message was sent or that the person became a qualified lead. That restraint is useful when later reporting tries to attach business meaning to a convenient event.
Return focus without trapping the page
The chooser moves focus to its first available option when opened. Escape dismisses it and returns focus to the shared button. It’s a nonmodal chooser: moving keyboard focus elsewhere closes it instead of trapping the person inside.
The provider’s minimize and hide callbacks also update the shared launcher state. When the chat closes after being active, focus returns to the shared button. Cleanup clears callbacks and hides the widget without deliberately ending the conversation during layout changes.
The component also measures the mobile call-to-action bars so the launcher can sit above them. Expanded chat changes that relationship again. Those details belong in the same integration review because a correct loader is little use if the button becomes hidden under another control.
Resolve the promise after closing in the test
The most useful cancellation test doesn’t stop at checking that Escape hides the chooser. It holds the loader promise open, dismisses the chooser, then resolves the promise. The assertion checks that maximize wasn’t called. A second test advances the timeout and then resolves the late result.
The committed review notes also record normal-browser opening, closing and reopening of the actual provider, plus narrow-screen and keyboard checks. They distinguish that from headless requests that received an error response, and they say no messages were sent.
That’s the level of evidence worth keeping with a widget change. Test the application state with controlled promises, check the real provider interaction separately, and make sure an old loading request can’t interrupt the task the person chose next.
Written by
Adrian Saycon
A developer with a passion for emerging technologies, Adrian Saycon focuses on transforming the latest tech trends into great, functional products.



