Re-checked 9 October
Update 5dbfd89, "Fix loaders, dropdowns and mobile menus", fixes seven of the eight items from the first review, the mobile notifications heading, and dropdowns that wouldn't open from their selected value. It doesn't break anything that was measured: 64 desktop page loads, 13 on a phone and 29 with failing requests all match today's app or improve on it. One item is only half fixed, the phone account menu is only partly fixed, and the re-check found two new issues.
| # | Item from 8 October | Status | Note |
|---|---|---|---|
| 1 | Loader space stays after the page loads | Fixed | Page heights after loading match today's app on every page checked, desktop and phone. |
| 2 | Signup address step shows an error | Fixed | The home address setup now waits for both the client and the step fields. |
| 3 | Notifications panel loader on every open | Fixed | The list shows straight away again. |
| 4 | Recipient details disappear | Fixed | The card stays; the payments error shows inside the payments section. |
| 5 | Same-currency confirmation | Fixed | Separate loading and error state for payments and recipients, with fallback copy. Checked in the code; no local data to test with. |
| 6 | Loader over buttons that work | Fixed | The loader now has its own space. Small nit: on first load the Unallocated funds box starts lower and jumps up when payments arrive. |
| 7 | Refer and earn loader never shows | Fixed | It shows on a cold load, and an existing console error on that page is gone. |
| 8 | Same-currency transfer page spins | Half fixed | A delivery dates failure now shows an error. With no same-currency balance it still spins: the error moved to isSelectedBalancePositive() (new-transfer.component.ts:192), which reads selectedBalance.balance while it's undefined. |
Reported separately, on phones
Notifications heading: fixed. The oversized X and the big gap above the title are gone; it's now a 60px header bar with a 44px close button, and it holds up at 320px wide.
Account menu: partly fixed. It's now a 240px panel against the right edge instead of floating 60px in from the left, and nothing is cut off at 320px. It's still a full-height panel with Logout at the bottom and about 485px of empty space between Settings and Logout. This isn't a new breakage: with the phone rules from before the 1 October changes put back, the menu looks the same as it does in production today.
Dropdowns: fixed. Clicking the selected value of a single-choice dropdown, for example the settlement account on a transfer, now opens the list. In production today it doesn't.
New, found on the re-check
An error from one transfer stays on the next
If a transfer page fails to load and the user then opens another transfer, from a notification for example, the second one loads fine but keeps "Unable to load this content" above it. Before the PR there was no banner to leave behind.
Why
The page reuses its component when only the transfer id changes. loadTrade() sets loadingFailed = true on a failure (trade-confirmation.component.ts:73) but nothing clears it for the next load. Recipient overview, rate alert overview, market order allocate and recipient approval follow the same pattern in the code.
Suggested fix
Reset the flag, and the old data, at the start of each load:
loadTrade(id) {
this.loadingFailed = false;
this.trade = undefined;
...
Changing the chart range covers the whole page
On a currency page, picking a range such as 1Y draws the loader over the whole page while the chart reloads. The breadcrumb, the range buttons, and Create a Rate Alert and Make a Transfer can't be clicked until it clears. Today the old chart stays until the new one arrives.
Suggested fix
Show the loader only for the first load, or limit it to the chart and leave the old chart in place while the new range loads.
Not new, but worth knowing
When the recipient edit page fails to load, the banner shows the raw HTTP status text: "Internal Server Error" locally. Production serves over HTTP/2, which has no status text, and Angular then reports it as "OK", so users would see an error that just says "OK". The PR's fallback copy (statusText || 'Unable to load recipient...', recipient-form-page.component.ts:174 and delete-recipient-invite-page.component.ts:57) never shows because the status text is never empty. Use the fixed copy instead.
First review, 8 October
- 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.





























