Amir Balazade

Amir Balazade

Front-end engineer

abalazade@gmail.comlinkedin.com/in/amirbalazade

Istanbul, open to remote

Index

The payment page, rebuilt

The surface a customer actually pays through. Taking it from Angular 20 to 22 and dropping zone.js meant first answering an awkward question about a component library.

Surface
Public checkout, five routes, crypto payments on Polygon and BSC
Work
Framework upgrade, zoneless migration, dependency removal, pixel reconciliation
Stack
Angular 22, signals, native dialog, SCSS

The dependency problem

The repo sat on Angular 20.3 while its sibling was already on 22, and every piece of shared guidance had to be written twice, once per repo. Going zoneless was the other reason to move: this is a public payment surface, and after an earlier ticket removed the state library there was nothing zone-dependent left. Dropping zone.js takes a polyfill out of every bundle and a whole category of "why did this not repaint" out of the app.

One thing blocked both. The component library in use peered Angular 17 or 18 and a zone version two minors behind the installed one — both constraints already unmet before anyone proposed an upgrade. It could not go to 21, could not go to 22, and was pinned to the exact dependency being deleted.

Two ways out, and why one is not viable here

The library could be walked up rung by rung to its current major. That major introduces a licence manager: an unregistered install renders a fixed-position banner inside a closed shadow root with all: initial, on every page, with no quiet-degradation mode. The free tier exists but the key is per-repository, seat-capped and dated.

On a page where someone is entering an amount and confirming a transfer, a third party that can draw an unstyleable banner over the interface is not a dependency, it is a liability. The app used six component instances from that library, one consumer each, plus 550 of the 670 lines in the theme partial styling components this app has never rendered.

Six components is an afternoon. The licence is forever. They came out, the action sheet became a native <dialog>, and the upgrade went ahead with nothing peer-locking it.

The proof is that nothing happened

Every route was captured before and after, against a deterministic mock layer rather than production, so the same eleven scenarios produce the same pixels on demand. Across the six captured screens, four came out byte-for-byte identical to the originals. Two changed, both by design: the currency sheet, which had been the library's action sheet and is now a native dialog, and the amount card.

Currency picker rendered as a docked panel by the third-party component library
Before: the library's action sheet, docked to the left edge.
The same currency picker rendered as a native dialog element
After: a native dialog, no library, same job.

Pixel reconciliation and the audit

A later pass reconciled every frame against the design file at 1920, then proved the layout at 390, 768, 1024 and 1440. Divergences were fixed where the difference was ours and reported with their node ids where it was not, rather than quietly conforming to whichever side happened to be open. One rule overrode the design outright: interface titles are Title Case, because consistency inside the product beats consistency with a file that nobody using the product can see.

Accessibility ran as an audit in the same pass, with axe injected into the browser sweep rather than run once at the end and declared clean.

The payment gateway at 390 pixels wide, with merchant details above the currency picker
390. Merchant details stack above the amount.
The same payment gateway at 1440 pixels wide, in two columns
1440. The same content, two columns.

What the mock layer bought

The screenshots on this page contain no real customers, because the whole capture runs against fixtures: an invented merchant, an invalid-by-design domain, a phone number from the reserved range. Eleven scenarios cover the paths that are otherwise hard to reach on demand — expired, already paid, wrong chain, user rejects, transaction reverts, slow, unsupported token.

It is a build-time file replacement rather than a runtime flag, which is the part that matters: a marker string was verified absent from the production bundle and present in the mock one, and the production bundle grew by 76 bytes for the two empty arrays left behind. Test scaffolding that can be switched on in production is not scaffolding, it is a feature nobody wrote a spec for.

The admin panel, same company, opposite decision