Skip to content

feat(support): let customers type the receiver IBAN instead of picking one - #1191

Merged
TaprootFreak merged 12 commits into
developfrom
feat/support-receiver-iban-free-text
Jul 28, 2026
Merged

feat(support): let customers type the receiver IBAN instead of picking one#1191
TaprootFreak merged 12 commits into
developfrom
feat/support-receiver-iban-free-text

Conversation

@TaprootFreak

@TaprootFreak TaprootFreak commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator

Why

When a customer reports that a transfer never arrived, the support form asks which account it went to. For TRANSACTION_MISSING that field was a required dropdown fed by GET /bank, which lists only the collective accounts.

A customer who deposits through a personal IBAN could never find their real receiver there — and because the field is required, they had to pick an account that is not theirs. That wrong value lands in the ticket and reads to support like a statement from the customer. There are over 3,000 personal IBANs in production, so this is the normal case, not the edge case.

What

The field is now free text, backed by checkReceiveIban from useBank().

The check is advisory and never blocks submission — a support ticket must never fail on an unrecognized IBAN. The field stays required only in the sense of "must not be empty"; a blank-only entry is rejected as empty, which the dropdown made impossible before.

The hints, all phrased without blaming the customer:

Status Hint
recognized We have recognized this IBAN.
not recognized We could not recognize this IBAN with the information available to us. You can submit your request anyway.
not a valid IBAN This does not look like a valid IBAN.
login needed Please log in so that we can also check your personal IBAN.
any failure, incl. HTTP 429 We could not check this IBAN at the moment. You can submit your request anyway.

NotMatched claims no more than the status supports. Per the library's JSDoc it is not a statement that DFX does not own the IBAN: another customer's personal IBAN is never checked, and one left behind by an account merge is not either. So the text says we could not recognize it with the information available to us — not that it could not be assigned, and certainly not that the customer mistyped. Telling the very customer this change was built for to re-check an IBAN that is already correct would push them back to guessing, the exact failure this removes.

Timing of the check, and of the hints. The call is debounced by 500 ms and only fires once the value reaches 15 characters after normalization — the endpoint is rate-limited and the limit is shared by everyone behind the same network, and the API answers incomplete input with InvalidIban by design. Normalization strips every non-alphanumeric character, matching what the API does before it looks an IBAN up, so a grouped or dash-separated entry is measured and sent exactly as the server will read it. The debounce sits on that normalized value, so retyping a space costs no request.

Because 15 characters is the shortest IBAN in existence and a Swiss one needs 21, the threshold proves length but not completeness. Display is therefore gated on focus: while the field is focused only the positive confirmation appears — it can occur only for a complete IBAN that matched — and every other verdict waits until the field is left. That keeps a wrong "this does not look like a valid IBAN" off the screen mid-typing and stops the form from jumping. Submitting by mouse still shows the hint, because focus leaves the input on mousedown, before the click is dispatched; submitting with Enter from inside the field shows none, which is acceptable for a check that blocks nothing.

Stale responses are discarded through a request counter, and the check refuses to run at all when the debounced value is no longer the value in the field, so no hint can appear under a value it was not computed for.

getBanks() and its state and effect are gone from this screen — banks had no other use here, and the error banner that used to appear when the bank list failed to load goes with it. The sender IBAN, date and transaction fields are untouched.

Dependency — the prerelease is the intended pin

DFXswiss/api#4391 and DFXswiss/packages#192 are both merged. The client call arrives here
through @dfx.swiss/react, pinned to 1.7.0-beta.0; @dfx.swiss/core follows transitively to
0.6.0-beta.0 and is not a direct dependency here.

The prerelease is the deliberate choice for this change, not a placeholder waiting on something
else: no release of the library to main is planned for it. Nothing about this PR is blocked on a
stable version, and reviewers should not hold it for one. Should a stable 1.7.0 be published
later, the range ^1.7.0-beta.0 accepts it, so an ordinary install picks it up without a second
range edit.

CI installs from the lockfile rather than the range, which is why the pin has to ship as part of
this change rather than only as a range bump.

What was verified locally, and how

These were run against the published package, not a local build of it.

Command Result
npm run lint exit 0
npm run build:dev (the pipeline's real TS compile) exit 0, build folder ready
npm run widget:dev (the stricter build) exit 0, widget folder ready
CI=true npm test -- --watchAll=false 39 suites, 470 tests passed
prettier --check on every changed and added file clean
dependency pin @dfx.swiss/react 1.7.0-beta.0, @dfx.swiss/core 0.6.0-beta.0 transitively
eslint on the added test files 0 problems

Note for reviewers: npm run lint globs {src,apps,libs,test}/**/*.ts and therefore does not cover .tsx — the only changed code file. It was linted explicitly and is clean, but a green lint job alone says nothing about this change.

Tests

The check had no coverage at all. Two things are pinned, because everything else about
this feature is cosmetic next to them: the check never blocks, delays or alters a
support request
, and a hint never belongs to a value that has left the field.

src/__tests__/support-issue-receiver-iban.test.tsx renders the screen and covers:

Behaviour What the test holds
Field visibility free text appears only for TRANSACTION_MISSING
Threshold 14 normalized characters make no request, 15 make exactly one
Normalization separators do not count towards the threshold and are not sent; a grouped IBAN arrives as the server reads it
Debounce fast typing produces exactly one request, for the final value
Formatting-only edit regrouping an already checked IBAN costs no second request
Reset any edit clears hint, spinner and unavailable state immediately, before a new answer arrives
Stale answer a late response for an earlier value cannot overwrite the current hint — whether it resolves or rejects
Bound to its value a verdict is shown only while the field still holds the IBAN it was computed for
Superseded value no request is made for a debounced value the customer has already changed
Focus gating positive confirmation shows at once; every other verdict waits for blur
Hint texts the four texts and the colour that separates the positive one
Failure a rejected check, rate limiting included, produces the unavailable hint and no form error
Spinner on while in flight, off on success and on failure
Never blocks the form is actually submitted while a check is still in flight, after a failure, for a non-IBAN entry, and for every one of the four verdicts
Never a form error a rejected check produces no error banner and no field error, for rate limiting and for a server error alike
Raw value the submitted request carries the customer's text unchanged, spaces and all
Required an untouched or blank-only field is rejected as empty; any non-empty value stays submittable
Reason change leaving TRANSACTION_MISSING clears the state and starts no further check

Each of these was checked by breaking the screen on purpose and confirming the named
test turns red — the guards, the threshold, the gating, the normalization, the
required rule, the failure path and the submit handler were each removed in turn.

Two things resist a single-line mutation, and neither is a gap in the tests.
ReceiverIbanCheckDelay set to 0 changes nothing, because useDebounce computes
delay || 500 and a falsy 0 becomes 500 again. And the reset effect shares its
three setState calls with the check effect's own guard branch, which runs on the
same dependency; removing a reset from one place leaves the other to compensate, so
only removing it from both turns the test red.

src/__tests__/support-issue-translations.test.ts pins the five new keys in de, fr and
it. It is deliberately scoped to those keys: the three files already differ on unrelated
entries, so a full parity test would fail on other people's gaps.

@dfx.swiss/react and @dfx.swiss/react-components are mocked rather than loaded —
their published ESM does not load under this repo's test command. The mocks reproduce
the rule merging and validation shapes the screen relies on and wire the input through
react-hook-form, so focus, validation and the watched value behave as in the app.

Visual regression baselines

e2e/support-issue-receiver-iban.spec.ts captures the six visible states of the field:
empty, checking, recognized, not recognized, invalid, and check unavailable. It follows
the pattern of e2e/realunit-support.spec.ts — real customer auth, synthetic fixtures,
and page.route intercepting only PUT /v1/bank/receiveIban while everything else goes
to the API. Type and reason come from the screen's own issue-type and reason URL
parameters, so no dropdown interaction is needed.

The baseline images are not in this commit. playwright.config.ts puts the platform
in the snapshot path and all existing baselines are *-chromium-darwin.png;
CONTRIBUTING.md asks for new ones to be generated on the same platform as the existing
ones. Generating them here would have produced *-chromium-linux.png with different font
rendering — different files sitting next to the existing set, which is worse than none.

To add them, on macOS with the local API stack up and a @dfx.swiss/react that has
checkReceiveIban installed:

REACT_APP_API_URL=http://localhost:3000 npx playwright test support-issue-receiver-iban.spec.ts --project=chromium --update-snapshots

This writes all six baselines in one pass. Per CONTRIBUTING.md these tests are a
review aid and do not run in CI, so their absence blocks nothing.

…g one

When a customer reports that a transfer never arrived, the form asks which account it
went to. That field was a required dropdown filled from GET /bank, which lists only the
collective accounts. A customer who deposits through a personal IBAN can never find their
actual receiver in that list, and because the field is required they had to pick an
account they never transferred to. The wrong IBAN then sits in the ticket and reads like
a statement from the customer - in exactly the situation where someone needs help. There
are more than three thousand personal IBANs in use, so this is the normal case rather
than an edge case.

The dropdown becomes a free-text field, and the API checks what was typed. The hint below
the field says whether the IBAN was recognised, whether it does not look like an IBAN at
all, whether it could not be attributed, or that a login is needed before personal IBANs
can be checked.

The check is advisory and never blocks submission. Any failure, rate limiting included,
shows a neutral "could not check right now" and leaves the form submittable - a support
ticket must never fail because an IBAN was not recognised. The field stays required in
the sense that it may not be empty.

Three things the client library does not handle, so the screen does: the input is
debounced by 500 ms, the value used elsewhere in the codebase, rather than checked on
every keystroke; a request counter discards answers that arrive after a newer one, so a
slow answer for a prefix cannot overwrite the answer for the complete IBAN; and the
counter is also bumped when the field is cleared, so an in-flight request cannot bring a
hint back.

The wording for a non-attributable IBAN deliberately avoids claiming that it is not a DFX
IBAN. The status does not carry that meaning: another customer's personal IBAN is never
checked, and after an account merge personal IBANs stay with the former account, so a
customer's own older IBAN can land there too.

getBanks is no longer used on this screen and is removed along with its state and effect.
The sender IBAN field, which comes from the customer's own bank accounts, is untouched.
@github-actions

Copy link
Copy Markdown

🤖 PR Review Bot

❌ TypeScript: 3 errors


This is an automated review. Please address the issues above.

…the hint

Review found three things the first version got wrong.

The check fired on every typing pause, and the API answers incomplete input with
InvalidIban by design. A customer copying an IBAN off a bank statement and glancing up
mid-way was told their IBAN looks invalid, with the form jumping as the hint appeared and
vanished. For the customers this change exists for that is the normal way of filling the
field, not an edge case, and every pause also spent a request from a limit shared by
everyone behind the same network. The call now waits until the normalized value reaches
fifteen characters, the shortest IBAN in use. A real typo is still caught, because the
customer stops at twenty-one or twenty-two characters and the checksum fails there.

The hint for an unattributable IBAN asked the customer to check whether it is correct.
The status does not support that: another customer's personal IBAN is never checked, and
after an account merge personal IBANs stay with the former account. So the one customer
this feature was built for - the one whose personal IBAN cannot be matched - was being
told to correct an IBAN that was already right, and replacing it with a guess would
restore exactly the wrong-IBAN-in-the-ticket problem this change removes. The hint now
says only that we could not assign it, and that the request can be submitted anyway.

A blank-only entry passed the required rule, sanitized to an empty string server-side and
produced a ticket with no receiver IBAN at all - something the dropdown made impossible.
An emptiness check now rejects it. It is a check on presence, never on the IBAN itself, so
an unrecognized value is still accepted.
…field

Two independent reviews showed the length threshold does not do what it was added for.
Fifteen characters is the shortest IBAN in existence and a Swiss one needs twenty-one, so a
customer pausing at sixteen still triggered a request and still read "this does not look
like a valid IBAN" while typing a correct one. The threshold moved the problem, it did not
solve it, and a country-length table in the frontend would only add a second authority on
IBAN validity next to the API.

What actually matters is which hint can do harm, and that is only the negative one. A
positive confirmation can occur only for a complete IBAN that matched, so it still appears
immediately; every other verdict now waits until the field loses focus. That also keeps the
form from jumping while someone is still typing.

The threshold itself was measuring the wrong string: it stripped only ASCII spaces while the
API strips every non-alphanumeric character, so a dash-grouped entry counted separators
towards the length and was checked too early. Normalization now matches the API exactly and
is the single source for the threshold, the request and the equality guard. The debounce sits
on the normalized value, so retyping a space or a dash costs no request.

The check could also start for a debounced value that was no longer in the field, if both
updates landed in one commit - the response then wrote a hint under the new or empty value.
Rather than reason about batching, the effect now refuses to run unless the debounced value
is still the current one.

And the unmatched wording is more careful again: the status does not establish that DFX does
not own the IBAN, since another customer's personal IBAN is never checked and neither is one
left behind by an account merge. The text now says we could not recognize it with the
information available to us.
@TaprootFreak

Copy link
Copy Markdown
Collaborator Author

This diff went through four review passes before two independent reviews came back with no findings. What they caught:

The length threshold did not do what it was added for. Fifteen characters is the shortest IBAN in existence and a Swiss one needs twenty-one, so a customer pausing at sixteen still triggered a check and still read "this does not look like a valid IBAN" while typing a correct one — the very behaviour the threshold was meant to prevent. It moved the problem rather than solving it. A country-length table in the frontend was considered and rejected: it would put a second authority on IBAN validity next to the API. Instead, display is now gated on focus. Only the positive confirmation appears while the field is focused, because it can occur only for a complete IBAN that matched; every other verdict waits until the field is left.

The threshold was measuring the wrong string. It stripped only ASCII spaces while the API strips every non-alphanumeric character, so a dash-grouped entry counted its separators towards the length and was checked far too early. Normalization now matches the API exactly and is the single source for the threshold, the request and the equality guard.

A check could start for a value no longer in the field. If the debounce timer queued a value while the field changed, both updates could land in one commit and the response would write a hint under the new or empty value. Rather than reason about batching, the effect now refuses to run unless the debounced value is still the current one.

The unmatched wording overstated twice. It first asked the customer to check whether the IBAN was correct — which imputes a mistake in exactly the cases the API documents as not mistakes: another customer's personal IBAN is never checked, and neither is one left behind by an account merge. Telling the customer this change was built for to re-check a correct IBAN would push them back to guessing. The text now says only that we could not recognize it with the information available to us, and that the request can be submitted anyway.

Both reviews independently verified the assurance that matters most here: the check never enters form validation, never disables submission, and the ticket endpoint accepts any string. An unrecognized IBAN cannot stop a customer from reporting a problem.

npm run lint, npm run build:dev, npm run widget:dev and 427 tests pass locally against the pending library build.

This PR stays in draft, and its CI will fail until the library release lands. See the ordering section above — the lockfile has to be updated in this branch after the release, which is a required step and not an automatic one.

The advisory receiver IBAN check had no test at all. These tests pin the two
properties the feature depends on: the check never blocks, delays or alters a
support request, and a hint never belongs to a value that has left the field.

The component tests render the screen and cover field visibility per reason, the
length threshold at both its boundaries, the normalization the request shares
with the API, debouncing, the fact that a formatting-only edit costs no request,
the reset that clears hint, spinner and unavailable state on every edit,
discarding a late answer for a superseded value whether it resolves or rejects,
the focus gating that holds back every non-positive verdict until the field is
left, the four hint texts and their colour, the unavailable path for a failed or
rate-limited check, the spinner, the requirement that the field be non-empty,
and submits that actually go through while a check is in flight and after one
has failed, proving the raw text the customer typed is what gets sent.

Each behaviour was checked by breaking the screen on purpose and confirming the
named test turns red. One constant cannot be pinned this way: setting
ReceiverIbanCheckDelay to 0 changes nothing, because useDebounce computes
delay || 500 and a falsy 0 becomes 500 again.

The language files get their own test for the five new keys, pinning the exact
text per language so a swapped or hollowed-out translation fails. It is scoped
to those keys: the three files already differ on unrelated entries, so full
parity would fail on gaps this change did not create.

The Playwright spec captures the six visible states of the field for review.
Its baselines are not part of this commit: they are platform-bound, and
CONTRIBUTING.md asks for them to be generated on the same platform as the
existing ones.

@dfx.swiss/react and @dfx.swiss/react-components are mocked rather than loaded,
because their published ESM does not load under this repo's test command. The
mocks reproduce the rule merging and validation shapes the screen relies on and
wire the input through react-hook-form, so focus, validation and the watched
value behave as they do in the app. Form deliberately renders no submit handler
of its own, matching the real component, so the button's own handler stays the
only path a submit test can take.
…check

CI installs from the lockfile, so the range alone does not move the build; the
pin has to ship with the change. This is the prerelease line, taken knowingly:
the stable release of the same version is not out yet, and the screen cannot
compile without checkReceiveIban.

The range is ^1.7.0-beta.0, which the stable 1.7.0 also satisfies, so a later
install moves off the prerelease without another range edit. @dfx.swiss/core
follows transitively to 0.6.0-beta.0; it is not a direct dependency here.

Verified against the published package rather than a local build: build:dev,
the stricter widget:dev, lint, and the full test suite at 465 tests.
The hint was cleared in an effect on the normalized value, and effects run after
the render. On the first render after an edit the previous verdict was therefore
still in state and was shown under the new value - most visibly the positive
confirmation, which is deliberately shown while the field still has focus. The
result now carries the IBAN it was computed for, and the hint is derived only
when that value still matches the field. Clearing it in the effect becomes
unnecessary and is gone, along with the same clearing in the check effect's
guard branch; the reset effect keeps only the request counter and the spinner.

Two tests did not hold what their names promised, both shown by mutating the
screen:

Validations.Iban was mocked as always passing, so adding real IBAN validation to
the receiver field left the suite green - while in the app it would reject a
free-text entry and block a support request, which is the one thing this screen
must never do. The mock now fails, and a test keeps a complete form submittable
with a non-IBAN value in the field.

The translate mock returns its key, so removing the translation call around the
hint also left the suite green, and every non-English customer would have seen
English text. A test now pins that the hint goes through translate under the
screens/support namespace.

The visual spec gained the LoginRequired state, which the docblock promised but
did not record, and now forces lang=en: it asserts English text while
authenticating as a real account, and the language of that account would
otherwise decide whether the selectors match.
The button mock dropped isLoading, while the real component disables itself on
disabled || isLoading. Putting the submit button into its loading state while a
check is in flight would therefore have blocked the very thing this screen
promises never to block, and the suite would have stayed green. The mock now
mirrors the real component, and the two in-flight tests fail against that change.

Four comments claimed things a reader cannot check here: how the service answers
incomplete input and how its rate limit is shared, how the input component
handles focus handlers internally, that the tested translation keys contain
colons - they contain periods - and that the visual baselines are deterministic
and free of production data, when only the check endpoint is intercepted and
everything else comes from whatever stack the run points at. Each now says what
holds in this repository, and the reasoning they carried is kept where it was
about this code.
…gative one

Submission was proven unaffected for an unrecognized IBAN, for a check still in
flight and for one that failed - but not for the positive verdict. Blocking the
button or normalizing the submitted value specifically when the IBAN was
recognized therefore left the suite green, and the customer who gets the
friendliest answer is the one whose request would have been swallowed.

The submission test now runs over all four statuses, so any verdict-specific
special case fails it.
…g comments

The non-IBAN test checked that the field carries no error and the button stays
enabled, but it never pressed the button. An onSubmit that quietly returned for
a receiver value below the check threshold therefore passed, while in the app
the customer's request would simply never have been created. The test now clicks
and asserts the request carries the raw value.

Two comments were wrong rather than merely unverifiable. One said the hints
appear only after the field is left - the positive confirmation deliberately
appears immediately, which is the whole reason focus gating is selective. The
other justified the missing autocomplete by saying the receiver IBAN is not the
customer's own; with a personal deposit IBAN it is exactly theirs, which is the
case this screen was rewritten for. The remaining reason, that the sender field
above already claims the IBAN autofill, is true and stays.

A third comment pointed into node_modules for its evidence and now describes
what the mock does and why.
…imiting

The guarantee that a failed check never becomes a form error was pinned against
rate limiting only. Reporting every other failure through the screen's error
banner therefore passed, and a customer whose check answered with a server error
would have seen a form error under an advisory field. The failure case now runs
over two kinds of rejection, and each of them also submits and asserts the raw
value, so the two former single-status tests are covered by one parameterized
case rather than duplicated.
@TaprootFreak

Copy link
Copy Markdown
Collaborator Author

Six review passes were needed before both independent reviews came back clean. The screen itself carried one real defect, and most of the rest were tests that did not hold what their names promised.

The defect. The check result was cleared in an effect on the normalized value, and effects run after the render. On the first render after an edit the previous verdict was therefore still in state and was shown under the new value — most visibly the positive confirmation, which is deliberately shown while the field still has focus. The result now carries the IBAN it was computed for, and a hint is derived only while the field still holds that value. Clearing it in the effect became unnecessary and is gone, together with the duplicate clearing in the check effect's guard branch.

Mocks that were plausible but not faithful. Three of them, each found by mutating the screen and watching the suite stay green:

  • The Form mock attached onSubmit to the native form element, which the real component does not — it forwards onSubmit only to named children. Removing the submit button's own handler therefore passed.
  • Validations.Iban was mocked as always passing, so adding real IBAN validation to the receiver field passed — while in the app it would reject a free-text entry and block a support request, which is the one thing this screen must never do.
  • The submit button mock ignored isLoading, while the real component disables itself on disabled || isLoading. Tying the button's loading state to the check therefore passed.

Guarantees that were pinned for one case only. Submission was proven unaffected for an unrecognized IBAN, but not for the positive verdict, not for a non-IBAN entry, and not for a failure other than rate limiting. Each of those is now covered, so a verdict-specific or error-specific special case fails a test. The translation wiring is pinned too: removing the translate call around the hints used to leave the suite green, and every non-English customer would have seen English text.

Two reported points were rejected with reasons rather than applied. Retrying anonymously on HTTP 401 cannot occur here, because the endpoint's guard treats an unusable token as an anonymous request rather than rejecting it. And reproducing the button's hidden prop in the mock would not help: the real component adds a CSS class, which has no effect in jsdom, so the mock would have to use the HTML attribute instead — a less faithful mock that merely happens to be observable.

The PR description was brought to the end state along the way: the ordering note now reflects that the API and library changes have landed, and the dependency pin is described for what it is.

@TaprootFreak
TaprootFreak marked this pull request as ready for review July 27, 2026 23:44
The spec drove /support/issue with a freshly registered account. The screen sits
behind useKycLevelGuard, so the app redirected to contact data and the Receiver
IBAN field never rendered: all seven tests timed out waiting for a field that was
not there.

The route setup now pins kyc.level in the GET /v2/user response, next to the
receiveIban mock the tests already install. Nothing changes on the server, so the
account other specs share keeps the state they rely on, and the screenshots no
longer depend on whatever level the local account happens to have.
The spec captures the field in seven states: empty, check in flight, recognized,
not matched, invalid, check unavailable and login required. Its baselines were
missing, so the spec had nothing to compare against.

Taken on macOS against a local API stack, as CONTRIBUTING requires, and confirmed
by a second run without --update-snapshots. No other baseline is touched.
@TaprootFreak

Copy link
Copy Markdown
Collaborator Author

The seven missing baselines for e2e/support-issue-receiver-iban.spec.ts are in — plus one fix that running the spec for the first time turned up.

The spec could not work as committed. /support/issue sits behind useKycLevelGuard, and a freshly registered account is below that level, so the app redirects to contact data and the Receiver IBAN field never renders. All seven tests timed out waiting for a field that was not there. The route setup now pins kyc.level in the GET /v2/user response, next to the receiveIban mock the tests already install. Nothing changes on the server, so the account the other specs share keeps the state they rely on, and the screenshots do not depend on whatever level the local account happens to have. The seven tests themselves are untouched — same selectors, same mock states, same waits, same screenshot names and options.

How the images were produced and checked

  • Generated on macOS against a local API stack, per CONTRIBUTING → Visual regression tests; the files carry the *-chromium-darwin.png suffix like the existing ones, and no other baseline was regenerated.
  • --update-snapshots: 7/7 green.
  • A second run without --update-snapshots: 7/7 green against exactly those files, with identical sha256 before and after — a run that is only green while writing proves nothing.
  • The local account was at KYC level 0 both before and after the run, which is the evidence that this leaves no server-side state behind.
  • Every image was opened and checked against the state its test claims: 01 empty field with placeholder, no hint; 02 filled and focused, the in-field spinner present and still no hint (in a still image the spinner is a small mark, since the comparison disables animations); 03 the green We have recognized this IBAN. while the field still has focus; 04–07 the grey hint for not matched, invalid, check unavailable and login required, each after blur.
  • No real data: local seed data, the ISO 13616 example IBAN, placeholder name and date.

One thing worth knowing. The Date of the transaction field renders new Date() as its placeholder, so all seven images contain the date they were taken on. Regenerating them on another day produces a small diff there. I measured it rather than guessed: that text is 174 pixels of ink, so even a completely different date changes about 350 pixels against the maxDiffPixels: 5000 each test allows — roughly a factor of 14 of headroom. Pinning the browser clock would remove it, but that would touch all seven tests for a difference below the tolerance, and the cause is in the screen itself rather than in this test. Happy to do it if you would rather have byte-stable baselines.

Playwright does not run in CI by design (CONTRIBUTING), so these files cannot affect the checks; CI is green.

@TaprootFreak
TaprootFreak merged commit 20b537c into develop Jul 28, 2026
6 checks passed
@TaprootFreak
TaprootFreak deleted the feat/support-receiver-iban-free-text branch July 28, 2026 08:56
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