> Source: https://upvera.tech/research/kestrel-measure-before-you-fix/
> Upvera improves the speed of its app through Kestrel, Upvera's performance program, which measures every screen before changing it and accepts a speed fix only when a before-and-after count shows that it worked.

UTR-26-07 · Research note · In progress

# Kestrel: measure before you fix

Upvera improves the speed of its app through Kestrel, Upvera's performance program, which measures every screen before changing it and accepts a speed fix only when a before-and-after count shows that it worked.

## Why measure first

A sense that an app is slow is a real signal, but it does not say where the time goes. Kestrel, Upvera's performance program, started in August 2026 with a wide scope: stutter, wasted work and anything that gets in a person's way. Its first lessons were about evidence.

Reading code produces candidates, not verdicts. Early on, each proposed fix had to survive a review whose only job was to refute it, and most did not. In one case, an animation thought to run constantly sat in a branch that rarely renders. That became a rule: before claiming what code costs, show that the path actually runs.

The build can mislead too. The same code on the same phone, compiled as a release build, can show none of the stutter seen in a development build. So a dropped frame in a development build is not evidence; frame verdicts come from release builds. Counts are different. How many requests a screen makes and how many times a card renders do not depend on how the app was compiled. So Kestrel accepts speed fixes on counts.

## Close the class, not the finding

Kestrel's principle is to close the class a finding belongs to, not only the finding. If the same mistake keeps appearing in new files, the fault is not in those files. It is the missing mechanism that would make the mistake impossible.

Two examples show the shape. Live counters share one realtime channel per app, with updates fanned out to cards on the device, instead of one subscription per card. Each store that holds the signed-in person's state registers itself in one place, so signing out clears all of it in one step. Neither fix is clever. Both remove a decision someone could forget.

There is one red line: no fix may make the experience worse. Before any code moves, the program writes down what must not change: look, motion, haptics, copy and data freshness. A fix that buys speed with any of those is rejected.

## Gauges on every screen

The current phase starts with instruments, not repairs.

-   **A screen ledger.** In development builds, a short window after every navigation records the requests a screen made, any request made twice, every full re-render of a card with the property that caused it, and the moment the content was ready. Each screen produces one line.
-   **A ready marker.** A small component marks the moment a screen's content is painted. It has no size, so adding it cannot move anything on the page. A screen without it reports itself as unmarked.
-   **A static scanner.** It walks the syntax tree of every route and its local imports and counts known heavy patterns. It decides nothing. It only orders the queue for measurement.

## Verify the instruments

A gauge is a claim too, so it gets checked. An early text search over-counted one list component because it matched comments, and the first version of the scanner under-counted it because it missed aliased forms. Inventory counts now come only from the syntax-tree scanner, checked against a manual review.

Old maps get the same treatment. Before anyone acts on earlier findings, each one is re-read against the current code, file and line. Some have closed in the meantime, some point at files that no longer exist, and some need more work. A map from two months ago is a list of candidates, not a plan.

## The acceptance rule

A speed fix is accepted only with a before-and-after ledger line for the screen it targets. If the number does not move, the change is rolled back.

A worked example. On the post detail screen, the ledger showed the post card rendering in full five times in its first two seconds, and it named the causes: two tap handlers written inline, so every render of the screen created new functions and broke the card's memoization, and the post data arriving once. Making the handlers stable left one full render: the data arriving.

The rule cuts both ways. A change to how a screen reacts to focus looked like a saving on paper, but the render count did not move, so it was rolled back. A fix that moves no number is not a fix; it is risk.

## Rules that stay fixed

Some rules are written down for every new screen: long lists use a recycling list, gestures run on the UI thread, images use one cached image component, and components do not attach their own navigation effects. A check in continuous integration holds them as a ratchet: a file may lower its count of exceptions, and a file that raises it fails the check.

Kestrel also defines one shared way to load, prefetch, refresh, update optimistically and show skeletons, so every screen does these things the same way. It has one decision rule: a screen never states something untrue in order to look fast. A remembered balance shown as current fails that test, so money and value data open only on a fresh number or on a skeleton. A failed optimistic action reverses in place and says so in one sentence. Its signals carry only labels fixed in code, such as a route pattern; no content or identity is recorded, and nothing from it leaves the phone.

## How the work proceeds

Repairs go family by family, in order of traffic, starting with the feed. Every family follows the same steps: write the behavior contract, measure, confirm the cause in the code, make the smallest fix, measure again, record both numbers. When the same class of problem keeps returning, its pattern goes on the wall. Kestrel reports counts, not impressions.

## References

-   [React memo](https://react.dev/reference/react/memo)
-   [RFC 5861, HTTP Cache-Control extensions for stale content](https://www.rfc-editor.org/rfc/rfc5861)
-   [FlashList](https://shopify.github.io/flash-list/)

[Next · UTR-26-08**An AI that never sees the number**](/research/an-ai-that-never-sees-the-number/)
