18 вер. 2026 р.
Hoverwise: PWA з книгою обліку, яка відмовляється вгадувати
Shared Expenses without the math headache — живий PWA, де книга обліку зберігає цілі числа, не кешує баланси, а встановлення на iOS є частиною продукту.
Shared Expenses without the math headache.
Найпростіше пояснити Hoverwise не списком функцій.
Уявіть чотирьох людей, які на тиждень винаймають квартиру. Один платить за житло. Інший — за продукти. Третій купує квитки. Вечерю ділять між трьома, але не порівну. Один чек у євро, інший у гривнях. Хтось посеред поїздки переказує лише частину боргу.
На п’ятий день ніхто не хоче збирати таблицю чи гортати чат, щоб зрозуміти, хто кому винен.
Саме для цієї сцени зібрано Hoverwise.
Hoverwise — живий продукт Paul Titov & Co. Він тримає спільну книгу витрат для друзів, поїздок і спільної квартири: створити групу, запросити людей, додати витрати й чеки, побачити хто кому винен, закрити борг частково або повністю й рухатися далі. Кейс описує поверхню продукту. Цей матеріал — архітектура, яка не має права ставати двозначною, щойно в системі з’являються гроші.
Цікавим для нас було не ще одне поле «сума».
Цікавим було вирішити, що ніколи не має стати неоднозначним, коли в систему входять гроші.
PWA був продуктовим рішенням, а не скороченням
У Hoverwise немає версії в App Store чи Google Play. Це навмисно.
Для такої утиліти встановлення не повинно бути умовою участі. Хтось створює групу під час поїздки, надсилає запрошення — і інша людина має змогти приєднатися, не шукаючи спочатку застосунок у сторі.
Водночас, коли Hoverwise стає частиною поїздки, він має поводитися ближче до застосунку, ніж до сайта.
Фронтенд — React 19.2, Vite 8 і TypeScript 5.9. Навігація як у застосунку: Friends, Groups, Transactions, Expense і Account живуть у нижній панелі, а hash-маршрути на кшталт /#groups роблять окремі стани адресованими.
Production-збірка статична.
І саме це має значення.
Hoverwise спроєктовано під звичайний віртуальний хостинг. У production немає процесу Node. Node існує на етапі розробки й збірки; npm run build пише статичний dist/.
Бекенд — PHP 8.2+ з одним front controller API на /api і MySQL 8. Автентифікація, курси, Apple Sign In, сповіщення й пошта живуть у невеликих бекенд-класах, а не в другому runtime застосунку.
- Статичний React / Vite
- PHP 8 API
- MySQL
- Сервісний працівник
- Web Push
Це обмеження з’явилося раніше за архітектуру.
Інтерфейс на React сам по собі не виправдовує постійний Node-сервер. Якщо продукт надійно працює як статика плюс PHP і MySQL, другий runtime — це ще одна поверхня деплою, ще один процес для нагляду і зазвичай дорожчий хостинг.
Для іншого продукту це може бути виправдано. Тут — ні.
Хостинг є частиною продуктової архітектури, а не післядумкою. Розробка програмного забезпечення тут — це API, автентифікація, запрошення, фото, курси й контракт, якому може довіряти статичний застосунок. Це не другий runtime.
Книга обліку починається з цілих чисел
Гроші в Hoverwise не зберігаються як числа з плаваючою комою.
Витрата 24.37 EUR стає цілим числом мінімальних одиниць:
Мінімальні одиниці
24.37 EUR→2437
У базі поля на кшталт amount_minor і owed_minor — unsigned BIGINT.
На межі вводу Hoverwise приймає і ,, і . як десятковий роздільник, дозволяє не більше двох знаків після коми й один раз конвертує в мінімальні одиниці. Далі книга обліку працює з цілими.
Це звучить як деталь реалізації, доки розподіл не дає дріб, який не можна точно представити.
Тоді це вже облік.
Відсотковий, рівний, частковий і точний розподіл зрештою відповідають на те саме питання: скільки мінімальних одиниць винен кожен?
Тому Hoverwise не тримає чотири конкуруючі інтерпретації витрати. Усі чотири режими зводяться до однієї канонічної owed_minor.
Інваріант простий:
Інваріант
sum(owed_minor)=total_minor
Якщо він не виконується, API відхиляє витрату з split_must_equal_total.
Інтерфейс може пропонувати різні способи виразити розподіл. Книга обліку отримує одну істину.
Цей поділ корисний і ширше: гнучкість інтерфейсу не повинна просочуватися в модель обліку.
Округлення не має права створювати гроші.
Кілька валют без претензії бути банком
Зараз Hoverwise підтримує UAH, USD і EUR.
Група явно задає, які валюти приймає, і витрату не можна зберегти поза цим набором. Користувач може обрати валюту відображення, якщо вона доступна в групі.
Курси зберігаються окремо в exchange_rates і беруться з Національного банку України.
Після 02:00 Europe/Kyiv бекенд може оновити курси з фіду НБУ. Якщо фіда тимчасово немає, у Hoverwise є датований запасний варіант наближених значень кінця серпня 2026.
Ці числа — саме механізм стійкості, а не заява, що Hoverwise публікує власний курс.
Конвертація йде через гривню. Мінімальні одиниці переводяться в значення вихідної валюти, проходять відповідні курси й знову заокруглюються в мінімальні одиниці на межі призначення.
Hoverwise не є банком, платіжним процесором чи еквайрингом. Він тримає книгу обліку. Користувач може зберегти номер картки в профілі, щоб інший учасник скопіював реквізити під час розрахунку, але Hoverwise цей переказ не обробляє.
Цю різницю ми свідомо лишаємо нудною й явною.
Чотири витрати не повинні стати дванадцятьма боргами
Записати витрати — лише половина продукту спільних витрат.
Друга половина — відповісти на питання, яке люди справді ставлять: хто кому має надіслати гроші й скільки?
Наївна реалізація може зберегти кожне парне IOU від кожної витрати. Це технічно точно й практично неприємно.
Hoverwise натомість рахує нетто кожного учасника:
Нетто
net=paid − owed + settlements
Боржників і кредиторів потім зводять підходом min-cash-flow. Більші борги й кредити матчаться першими, і граф стискається до невеликого набору переказів.
Мета — не змусити користувачів розуміти історію графа.
Мета — сказати, що робити далі.
Тут є важне ускладнення: люди закривають борги, поки поїздка ще триває.
Hoverwise підтримує часткові розрахунки й свідомо не робить глобальний rematch усього історичного графа після кожного пізнішого платежу. Розрахунки після останньої витрати зменшують відповідні ребра, а не вигадують сторонні нові стосунки між учасниками.
Якщо Дарина платить тому, кого показав Hoverwise, ця дія не повинна раптом змусити інтерфейс стверджувати, що Алекс тепер винен Наталії лише тому, що можна побудувати математично еквівалентний граф.
Математична еквівалентність не завжди є продуктовою.
Екран розрахунку — це інструкція людині. Коли за цією інструкцією вже відбувся реальний платіж, зберегти її сенс важливо.
Сервісний працівник не повинен володіти грошима
Hoverwise використовує сервісний працівник, зараз із кешем оболонки hoverwise-mobile-v151.
Саме тут архітектура PWA стає небезпечною, якщо «офлайн» ставлять галочкою.
Правило таке: кешуйте застосунок. Ніколи не кешуйте книгу обліку.
Сервісний працівник може кешувати оболонку: /, вебманіфест, хешовані ассети й самого себе. Оболонка йде network-first і може віддати кешовану версію, якщо мережа зникає.
Але /api/ ніколи не кешується.
Так само /uploads/.
Якщо мережі немає, Hoverwise все ще може відкрити оболонку. Він не повинен вигадувати поточний баланс із застарілих відповідей API.
PWA з погодою, який показує вчорашню температуру, дратує. Фінансова книга, яка подає вчорашній баланс як сьогоднішню істину, — інша категорія збою.
Те саме зі сповіщеннями. Web Push через VAPID і minishlink/web-push може сказати, що з’явилася нова витрата чи розрахунок. Сповіщення не змінює книгу обліку. Авторитетний запис іде лише через API.
Є також дія Update app. Вона чистить кеш оболонки — HTML, JavaScript, CSS і ассети сервісного працівника — без виходу з сесії й без того, щоб дані API чи завантажені чеки вважати одноразовим кешем.
Застарілий фронтенд можна оновити.
Застарілі гроші не можна подавати як факт.
«Додати на домашній екран» в iOS — це функція
Маніфест сам по собі не створює досвід встановлення.
Hoverwise оголошує display: standalone, портретну орієнтацію, темну тему, start_url: /#groups, Apple mobile-web-app метадані та Apple touch icon. У standalone інтерфейс може це виявити й підлаштуватися.
Жодне з цього не вирішує найважче на iPhone: користувач усе одно має знати, як встановити.
Тому на iOS Hoverwise трактує встановлення як продуктовий сценарій. Інтерфейс пояснює, що шлях іде через Safari: Поділитися → Показати більше → Додати на домашній екран.
Ми не сховали це в документації.
Ми зібрали під це окремий UI-шит.
Це важливо, бо встановлення PWA — не технічний стан. Це користувацький шлях. Якщо операційна система робить його неочевидним, продукт має це компенсувати. Це така сама робота зі споживчими утилітами, як і фронтенд.
Після встановлення Hoverwise відкривається одразу в досвід групи й поводиться як та мала утиліта, якою має бути: те, що відкривають із домашнього екрана біля столика в ресторані, а не сайт, який треба згадати й знайти пізніше.
Що ввійшло в перший live
Перша жива версія вже закриває повний основний цикл.
Користувачі можуть створити акаунт через email і пароль, Google OAuth або Apple Sign In; керувати профілем, мовою й валютою за замовчуванням; знаходити друзів або запрошувати тих, хто ще не зареєстрований; створювати групи з ролями, датами, фото й локаціями; запрошувати учасників через друзів, email або посилання з токеном.
Витрати підтримують кількох платників, до п’яти фото чека й рівний, точний, відсотковий або частковий розподіл. Групи можуть обмежувати валюти й за бажанням вмикати взаємні розрахунки.
Є особистий перегляд витрат із сортуванням, денними графіками й, якщо в групи є локація, контекстом погоди. Локації груп використовують Leaflet і OpenStreetMap/Nominatim, а не власний картографічний стек.
Розрахунки можуть бути частковими, їх можна редагувати й видаляти. Значущі зміни пишуться в стрічку активності, а push покриває нові витрати й розрахунки.
Поїздку можна завершити. Завершена група стає лише для перегляду, і за звичайних прав її не закрити, поки лишається незакритий борг.
Є дрібниці, які непропорційно багато важать у щоденному користуванні: pull-to-refresh, кроп фото профілю й групи, приховування закритих неактивних груп і друзів, зовнішні посилання на альбоми, видалення акаунта й сесії, розраховані на застосунок, який відкривають нерівномірно протягом місяців.
Що далі розвивається — усе навколо цього ядра.
Ми продовжуємо працювати над продуктом, крайовими випадками й досвідом реальної групової поведінки. Перший live ми не вважаємо доказом, що розв’язано кожен можливий сценарій витрат.
Але архітектурні межі вже навмисно жорсткі.
Три правила, які лишилися б, якби ми збирали це завтра
Hoverwise дав нам три правила, які ширші за софт для спільних витрат.
- Нормалізуйте гроші рано. В інтерфейсі можуть бути чотири режими розподілу, три валюти й десятки екранів. У книги обліку має бути одне канонічне представлення, а інваріанти мають перевірятися на межі API.
- Кешуйте оболонку, не істину. Офлайн-досвід цінний, але сервісний працівник ніколи не повинен тихо стати альтернативною базою для даних, свіжість яких змінює їхній сенс.
- Обмеження інфраструктури належать до продуктового дизайну. Якщо споживчу утиліту можна випустити як статичну збірку React із невеликим PHP API та MySQL на звичайному віртуальному хостингу, ми не додаємо другий runtime лише тому, що фронтенд написаний на React.
Hoverwise виглядає просто, бо задуманий вихід простий: хтось заплатив, кілька людей винні, і тепер усі мають точно знати, що далі.
Щоб відповідь лишалася простою, довелося бути суворими всюди під нею.
Живий продукт — на hoverwise.app. Hoverwise зібрано в Paul Titov & Co, де споживчі утиліти роблять як продукти, а не як ще один лендинг.
Оновлено
Почнімо розмову
Робите PWA, якому довіряють гроші?
Paul Titov & Co проєктує споживчі утиліти як продукти — з встановленням, чесним обліком і архітектурою під звичайний хостинг, зокрема студійний Hoverwise.