At a glance
- 1Loader space stays after the page loadsFix before mergeBlank gaps on seven screens. Worst on Add account, where Next drops below the fold on a phone.
- 2Signup address step shows an error on a normal loadFix before mergeA red "Unable to load" banner above a form that loaded fine.
- 3Notifications panel shows a loader every time it opensYour callThe list it already has is hidden until the refresh returns.
- 4Recipient details disappear if their payments fail to loadWorth fixingThe whole card goes, including Edit and Make a transfer.
- 5Same-currency confirmation can vanish or spin foreverWorth fixingDepends on the recipients request, and on errors having a message.
- 6The loader is drawn over buttons that already workWorth fixingAllocate Recipient is covered, and blocked, while payments load.
- 7The refer and earn loader never showsSmallOne-line fix.
- 8The same-currency transfer page can spin foreverNot newHappens today too. A one-line service fix lets the PR's new error handling work.
What works
- Loaders now show on about 25 pages that used to sit blank while loading, inside the content area and below the headings.
- Failed requests no longer leave a spinner. With each page's own requests forced to fail, six pages used to spin forever: transfer history, trade history, authorised users, new quote, same-currency transfer and the duplicate check. Now every one clears and says what happened.
- Three console errors that happen today are gone, on the same-currency transfer page, rate alerts and market orders.
- No new console errors. It compiles and type-checks cleanly, merges cleanly into today's development, and CI passes. A real quote still goes from the form to Book.
Loader space stays after the page loads
The new loading regions keep their minimum height after the data arrives: 380px, 200px for the small ones, and 240px in the notifications panel. Wherever the real content is shorter, the leftover shows as blank space. The orange boxes mark it.
Why
.ct-loading-content sets min-height: 380px, and .ct-loading-content-small 200px (loading-state.component.scss:36). The class stays on the wrapper after loading. The notifications body now also has min-height: 240px (notification-list.component.scss:22).
Suggested fix
Hold the space only while the loader is showing. One change in loading-state.component.scss covers every place that uses the class:
.ct-loading-content {
display: block;
position: relative;
&:has(> ct-loading-state .loading-state-container) {
min-height: 380px;
}
&.ct-loading-content-small:has(> ct-loading-state .loading-state-container) {
min-height: 200px;
}
}
Keep the existing .ct-flex rule, and treat .notification-body's 240px the same way.
Tried in the local app by injecting this CSS into the PR build. Page heights after loading match today's exactly (Add account 890px, rate alerts 811px, transfer confirmation 1,882px), and the regions still hold 380px and 200px while loading.
Signup address step shows an error on a normal load
On the personal address step of signup, the page shows "Unable to load this content. Please refresh the page to try again." above a form that loaded fine. Refreshing doesn't help, because it usually happens on a fresh load.
Why
The page loads the step fields and the client in parallel, and the client takes two requests. When the step fields arrive first, getStepFields() reads this.client.different_home_address while client is still undefined (personal-address-page.component.ts:83). Today that error goes unhandled and the page carries on. The PR's new .catch (line 91) turns it into loadingFailed.
Suggested fix
if (this.client?.different_home_address) {
Or request the step fields once the client has arrived. The same .then(...).catch(() => this.loadingFailed = true) shape is used across the PR, so any error inside a success handler now reads as a load failure. Worth a quick check on the other signup steps.
Notifications panel shows a loader every time it opens
Opening the bell used to show the last list straight away and refresh it quietly. With the PR, the list is replaced by "Loading Notifications" until the refresh returns, on every open. If that's intended, nothing to do.
Why
The panel's isLoading now comes from notificationService.isFetchingNotifications, and opening the panel always calls refreshNotifications().
Suggested fix
If the loader should only cover the first load, use the condition the recent activity card already uses:
[isLoading]="notificationService.isFetchingNotifications
&& !notificationService.isNotificationsLoaded"
Recipient details disappear if their payments fail to load
If only the request for a recipient's payments fails, the whole recipient card goes, including Edit recipient and Make a transfer. Only an error and the invite card remain.
Why
The card now waits for both requests: @if (recipient && payments), where it was @if (recipient). The payments section already has its own @if (payments).
Suggested fix
Keep @if (recipient) for the card, and show the payments error inside the payments section.
Same-currency confirmation can vanish or spin forever
Two cases on the same-currency transfer confirmation.
- If the recipients lookup fails, the whole confirmation is replaced by an error: title, Save as PDF, broker, totals and payments. The content now requires recipients too:
@if (trade() && payments && recipients). Before, everything except the payments list still showed. - If a request fails without a JSON body, the loader never stops. That covers a network error, or an HTML 502 page during a deploy. The loader waits for
error(), which is set fromUtilsService.getError(err).message, and that is undefined in those cases.
No screenshot: the local demo data has no same-currency transfers. Both cases come from reading the code.
Where
same-currency-confirmation.component.html:1-6, and the error handlers in same-currency-confirmation.component.ts:58 and :82.
Suggested fix
Keep @if (trade() && payments) and require recipients only around the payments list. Drive the loader from explicit loading and failed flags, with fallback copy when the error has no message.
The loader is drawn over buttons that already work
On the transfer confirmation, the Unallocated funds box and its Allocate Recipient button appear straight away. The loader is then drawn on top of them while payments load, and clicks are blocked until it clears. It comes back every time payments reload: after allocating, approving or rejecting, and after a mass payment.
Why
ct-loading-state is position: absolute over the whole region, at z-index 8. With transparent, the content shows through but can't be clicked. loadPayments() turns it on at the start of every call (recipients-allocate-page.component.ts:178).
Suggested fix
Hide the content while its loader shows, or skip the overlay when the content is already there, as on a reload.
The refer and earn loader never shows
referralData starts as an empty object, so the loader's !referralData check is never true and the page renders before its data arrives. Confirmed on a cold load: no loader appears.
Where
refer-and-earn-page.component.ts:27, and the loader at refer-and-earn-page.component.html:7.
Suggested fix
referralData: any;
The template already waits with @if (referralData), so this should also stop an existing console error on a cold load (accept_invitation of undefined).
The same-currency transfer page can spin forever
Two cases where the loader on the same-currency transfer page never clears. Both happen today as well, so they aren't caused by the PR, but the first one stops the PR's new error handling from working.
- If delivery dates fail to load.
refreshSameCurrencyTransferData()reports the error on the supported-currencies stream instead of its own (currencies.service.ts:111), so the new error handler ingetDeliveryDates()never runs. - If the account has no balance that supports same-currency transfers.
selectBalance(this.balances[0])throws onundefined, so the delivery dates are never requested.
Suggested fix
// currencies.service.ts, refreshSameCurrencyTransferData()
this.sameCurrencyTransferDataObject.error(error);
And guard the empty balance list before selectBalance().
How this was tested
- Built the PR and the commit it branches from, and served each to the local app.
- Loaded 40-plus pages on each build with three demo accounts (a personal account with one recipient, one with 22 transfers, and a business authorised user), at 1280px and 390px wide.
- Slowed requests down to see the loaders, and forced each page's own requests to fail to check the loaders clear.
- Compared every page after loading, pixel by pixel. 28 of 41 were identical; the differences are the cases above.
- Read the whole diff line by line, separately from the browser testing.
- The Angular unit tests can't run on development at the moment, so the new integration spec wasn't run. It does type-check.























