18 Sept 2026

Hoverwise: PWA with a ledger that refuses to guess

Shared Expenses without the math headache — a live PWA whose ledger stores integers, never caches balances, and treats iOS install as product work.

Shared Expenses without the math headache.

The easiest way to explain Hoverwise is not with a feature list.

Imagine four people renting an apartment for a week. One pays for the apartment. Someone else covers groceries. A third person buys train tickets. Dinner is split between three people, but not equally. One expense is in euros, another in hryvnias. Someone transfers part of what they owe halfway through the trip.

By day five, nobody wants to reconstruct a spreadsheet or scroll through a group chat to understand who owes whom.

This is the scene Hoverwise was built for.

Hoverwise is a live product by Paul Titov & Co. It keeps a shared expense ledger for friends, trips and shared apartments: create a group, invite people, add expenses and receipts, see who owes whom, settle partially or completely, and move on. The work note covers the product surface. This note is the architecture that had to stay unambiguous once money entered the system.

Hoverwise group screen for a trip, with expenses, balances, and how much you are owed
A group in Hoverwise — who paid, what you are owed, and the next action on one screen.

The interesting part for us was not building another expense form.

It was deciding what must never become ambiguous once money enters the system.

A PWA was a product decision, not a shortcut

Hoverwise has no App Store or Google Play version. That is intentional.

For this kind of utility, installation should not be a prerequisite for participation. Someone creates a group during a trip, sends an invitation, and another person should be able to join without first finding an app in a store.

At the same time, once Hoverwise becomes part of the trip, it should behave much closer to an application than a website.

The frontend is React 19.2, Vite 8 and TypeScript 5.9. Navigation is application-like: Friends, Groups, Transactions, Expense and Account live in a bottom navigation bar, while hash routes such as /#groups make individual states directly addressable.

The production build is static.

And that last detail matters.

Hoverwise was designed to run on ordinary virtual hosting. There is no Node process running in production. Node exists during development and build time; npm run build produces the static dist/ bundle.

The backend is PHP 8.2+ with a single API front controller at /api, backed by MySQL 8. Authentication, exchange rates, Apple Sign In, push notifications and email are handled by small backend classes rather than by introducing another application runtime.

This constraint came before the architecture.

A React interface does not automatically justify a permanent Node server. If a product can run reliably as static assets plus PHP and MySQL, adding a second runtime means adding another deployment surface, another process to supervise and usually a more expensive hosting requirement.

That may be justified for another product. It was not justified here.

Hosting is part of product architecture, not an afterthought. Software engineering here is the API, auth, invitations, photos, rates, and the contract the static app can trust. It is not a second runtime.

The ledger starts with integers

Money in Hoverwise is not stored as floating-point currency values.

An expense of 24.37 EUR becomes an integer number of minor units:

The database stores fields such as amount_minor and owed_minor as unsigned BIGINT values.

At the input boundary, Hoverwise accepts both , and . as decimal separators, allows no more than two decimal places and performs one conversion into minor units. From that point, the ledger works with integers.

This sounds like an implementation detail until a split produces a fraction that cannot be represented exactly.

Then it becomes accounting.

A percentage split, equal split, share-based split and exact-amount split all eventually have to answer the same question: how many minor units does each person owe?

Hoverwise therefore does not maintain four competing interpretations of an expense. All four split modes are normalized into the same canonical owed_minor representation.

The invariant is simple:

If it does not hold, the API rejects the expense with split_must_equal_total.

The UI can offer different ways of expressing a split. The ledger gets one truth.

That separation has proved useful elsewhere too: interface flexibility should not leak into the accounting model.

Rounding is not allowed to create money.

Multiple currencies without pretending to be a bank

Hoverwise currently supports UAH, USD and EUR.

A group explicitly defines which currencies it accepts, and an expense cannot be saved in a currency outside that set. Each user can also select a preferred currency for display when that currency is available in the group.

Exchange rates are stored separately in exchange_rates and sourced from the National Bank of Ukraine.

After 02:00 Europe/Kyiv, the backend can refresh rates from the NBU feed. If that feed is temporarily unavailable, Hoverwise has a dated fallback based on approximate late-August 2026 values.

Those fallback numbers are exactly that: a resilience mechanism, not a claim that Hoverwise publishes its own exchange rate.

Conversion goes through UAH. Minor units are converted to the source currency value, converted through the relevant rates, and rounded back into minor units at the destination boundary.

Hoverwise is not a bank, payment processor or acquiring product. It keeps the ledger. A user may save a card number in their profile so another participant can copy the recipient’s details while settling a debt, but Hoverwise does not process that transfer.

That distinction is deliberately kept boring and explicit.

Four expenses should not become twelve debts

Recording expenses is only half of a shared-expense product.

The other half is answering the question people actually ask: who should send money to whom, and how much?

A naïve implementation can preserve every bilateral IOU created by every expense. It is technically accurate and practically unpleasant.

Hoverwise instead calculates each participant’s net position:

Debtors and creditors are then matched using a min-cash-flow approach. Larger debts and credits are matched first, reducing the graph to a small set of transfers.

The goal is not to make users understand the history of the graph.

The goal is to tell them what to do next.

There is an important complication here: real people settle debts while a trip is still running.

Hoverwise supports partial settlements, and we deliberately avoid globally rematching the entire historical graph after every later payment. Settlements made after the latest expense reduce the relevant edges instead of inventing unrelated new relationships between participants.

If Darina pays the person Hoverwise told her to pay, that action should not suddenly make the interface claim that Alex now owes Natalia simply because a mathematically equivalent graph can be constructed.

Mathematical equivalence is not always product equivalence.

A settlement screen is an instruction to a human being. Once that instruction has caused a real-world payment, preserving its meaning matters.

Service workers should not own money

Hoverwise uses a service worker, currently with the hoverwise-mobile-v151 shell cache.

This is where PWA architecture can become dangerous if “offline support” is treated as a checkbox.

Our rule is: cache the application. Never cache the ledger.

The service worker can cache the shell: /, the web manifest, hashed assets and the service worker itself. The shell uses a network-first strategy and can fall back to its cached version if connectivity disappears.

But /api/ is never cached.

Neither are /uploads/.

If the network disappears, Hoverwise can still open its application shell. It must not fabricate a current balance from stale API responses.

A weather PWA showing yesterday’s temperature is annoying. A financial ledger presenting yesterday’s balance as today’s truth is a different category of failure.

The same principle applies to push notifications. Web Push, implemented with VAPID and minishlink/web-push, can tell a user that a new expense or settlement exists. A notification does not mutate the ledger. The authoritative write still happens through the API.

We also provide an Update app action. It clears the cached application shell — HTML, JavaScript, CSS and service-worker assets — without logging the user out and without treating API data or uploaded receipts as disposable cache.

Stale frontend code can be refreshed.

Stale money must not be presented as fact.

“Add to Home Screen” on iOS is a feature

A manifest alone does not create an install experience.

Hoverwise declares display: standalone, portrait orientation, a dark theme, start_url: /#groups, Apple mobile-web-app metadata and an Apple touch icon. When launched standalone, the interface can detect that state and adapt accordingly.

None of that solves the hardest part on iPhone: the user still has to know how to install it.

On iOS, Hoverwise therefore treats installation as an actual product flow. The interface explains that installation happens from Safari through Share → View More → Add to Home Screen.

We did not hide that information in documentation.

We built a dedicated UI sheet around it.

That distinction matters because PWA installation is not really a technical state. It is a user journey. If the operating system makes that journey non-obvious, the product has to compensate. That is consumer utility work as much as front-end work.

Once installed, Hoverwise opens directly into the group experience and behaves much more like the small utility it is supposed to be: something you open from the home screen while standing at a restaurant table, not a website you remember to search for later.

What made the first live

The first live version already covers the complete core loop.

Users can create accounts with email and password, Google OAuth or Apple Sign In; manage their profile, language and preferred currency; find friends or invite people who have not registered yet; create groups with roles, dates, photos and locations; and invite participants through friends, email or tokenized links.

Expenses support multiple payers, up to five receipt photos and equal, exact, percentage or share-based splits. Groups can restrict currencies and optionally enable mutual settlements.

There is a personal expense view with sorting, daily charts and, when a group has a location, local weather context. Group locations use Leaflet and OpenStreetMap/Nominatim rather than a custom mapping stack.

Settlements can be partial, edited or removed. Activity is recorded for meaningful changes, while push notifications cover new expenses and settlements.

Trips can also be completed. A completed group becomes view-only, and under normal permissions it cannot be closed while unresolved debt remains.

There are smaller details that matter disproportionately in daily use: pull-to-refresh, cropped profile and group photos, hiding settled inactive groups and friends, external album links, account deletion, and persistent sessions designed for an app people may open irregularly over the course of months.

What is still evolving is everything around that core.

We are continuing to work on the product, its edge cases and the experience around real group behaviour. We are not treating the first live version as proof that every possible expense scenario has been solved.

But the architectural boundaries are already intentionally hard.

Three rules we would keep if we rebuilt it tomorrow

Hoverwise gave us three rules that extend beyond expense-sharing software.

  1. Normalize money early. Interfaces can have four split modes, three currencies and dozens of screens. The ledger should have one canonical representation and enforce its invariants at the API boundary.
  2. Cache the shell, not the truth. Offline UX is valuable, but a service worker should never quietly become an alternative database for information whose freshness changes its meaning.
  3. Infrastructure constraints belong in product design. If a consumer utility can ship as a static React build with a small PHP API and MySQL database on ordinary virtual hosting, we do not add another runtime merely because the frontend happens to use React.

Hoverwise looks simple because the intended output is simple: someone paid, several people owe, and now everyone should know exactly what happens next.

Keeping that answer simple required being strict everywhere underneath it.

You can open the live product at hoverwise.app. Hoverwise is built by Paul Titov & Co, where we build consumer utilities as products rather than treating them as another landing page.

Updated

Start a conversation

Building a PWA that has to keep a ledger?

Paul Titov & Co designs consumer utilities as products — installable, honest about money, and sized to ordinary hosting — including studio software such as Hoverwise.