TrainerPad - CRM для тренеров, которая работает без сети и сама разруливает конфликты синхронизации
Персональные тренеры ведут клиентов и тренировки из зала, где интернета либо нет, либо он рвётся. Обычный PWA с кэшем эту проблему не решает: как только два устройства расходятся в офлайне, данные нужно мирить, а «последний записавший прав» здесь означает потерянную тренировку.
Написал продукт один, с нуля: 626 коммитов за полгода, около 67 000 строк TypeScript. React с Vite и Zustand, Dexie поверх IndexedDB на клиенте, Express с Prisma и PostgreSQL на сервере.
Движок синхронизации
Ядро - собственный движок на 646 строк. Очередь изменений живёт в IndexedDB со своими правилами
слияния: создание и следующая за ним правка схлопываются в создание, правка и удаление - в удаление,
иначе сервер получает операции над записью, которой у него ещё нет. Цикл синхронизации идёт каждые
30 секунд и запускается мгновенно при возврате на вкладку. Два открытых окна браузера разводит
блокировка через navigator.locks - без неё они наперегонки отправляют одну и ту же очередь.
Догрузка с сервера идёт с нахлёстом в две секунды и приоритетом локальных изменений: нахлёст
закрывает дыру от рассинхрона часов, приоритет гарантирует, что свежая правка из зала не затрётся
ответом сервера. Баланс клиента пересчитывается локально, а не принимается от сервера на веру.
До этого стоял готовый PowerSync. Сравнил его с RxDB, ElectricSQL и Turso и мигрировал на связку Dexie с обычным REST, сняв около 6300 строк чужого рантайма. Это не борьба с библиотекой ради принципа: набор конфликтов в тренерском сценарии оказался узким и специфичным, и описать его своими правилами вышло дешевле, чем гнуть общее решение под себя.
Как это выглядит в жизни: однажды пришлось восстанавливать 154 упражнения пользователя из его же локальной базы после сбоя. Для таких случаев в продукте есть принудительный ре-синк и экспорт локальной базы в Telegram.
Всё остальное тоже один
Продукт живёт в проде на своём сервере: HTTPS с security-заголовками, рядом админка и самостоятельный мониторинг ошибок на self-hosted GlitchTip.
Поисковикам одностраничное приложение отдаётся заранее отрисованным: пререндер вынесен в CI - Chrome на раннере обходит публичные маршруты и кладёт готовый HTML со структурированными данными. Семантическое ядро описано прямо в коде, рядом с маршрутами.
Блог пишется полуавтоматически: по расписанию headless-агент готовит черновики статей и обложки, я вычитываю и публикую. То есть один человек ведёт продукт, инфраструктуру, поисковую оптимизацию и контент-машину разом.