Crimson.Lab

Лаборатория Кримсона

26 August 2026

Дневник питания - оффлайн и без сервера

logo

Врач попросил вести дневник питания - время и что съел. Все готовые приложения хотят регистрацию, подписку, и именно времени приёма пищи - нет у многих. Написал своё: eat.x-crm.in - открыл, записал, закрыл. Бесплатно, с синхронизацией через гугл аккаунт.

Что умеет

Без сервера - это как?

Буквально: бэкенда нет. Статика лежит на GitHub Pages, а синхронизация ходит из браузера напрямую в Google Drive API - в скрытую папку приложения (appDataFolder), которую не видно среди файлов Диска и до которой другие приложения не дотянутся.

Авторизация - Google Identity Services, token flow: никакого client_secret, никакого промежуточного сервера, который читает ваши данные. Access token живёт около часа и продлевается только по клику - при запуске приложение не открывает никаких окон и вообще не ходит в сеть.

Источник истины - локальная база. Облако лишь реплика: слияние по правилу last-write-wins на каждую запись, удаления разносятся tombstone-ами и вычищаются через 30 дней. Одновременная правка на двух устройствах не превращается в кашу - побеждает более поздняя версия.

Калории - чужой моделью, но на своём ключе

Считать калории руками - тоска, а «умные» трекеры просят за это подписку. Здесь сделано дёшево и честно: вы вписываете свой ключ OpenRouter в настройках, и браузер ходит в их API напрямую - CORS у них открыт, бэкенд опять не нужен.

Выглядит это так: у записи появляется кнопка «≈», по клику модель получает текст записи и возвращает одно число - примерные ккал. Число ложится в саму запись, едет на другие устройства вместе с ней и стирается, если текст записи изменили: старая оценка к новому тексту уже не относится. Никаких фоновых запросов - не нажали, ничего никуда не ушло.

По умолчанию стоит mistralai/mistral-small-3.2-24b-instruct - победитель замера в августе 2026: правдоподобные оценки на всех пробах, около 0.4 с и порядка $0.0005 за сотню оценок. Модель меняется в настройках, список подтягивается из OpenRouter. Рассуждающие модели брать не советую - они жгут невидимые reasoning-токены, и реальная цена выходит в десятки раз выше заявленной.

Ключ хранится вместе с остальными настройками и едет на другие устройства тем же файлом на Google Диске. Внутри файла он в base64 - это защита от случайного взгляда, а не шифрование, и притворяться иначе не буду.

Приватность

Данные хранятся ровно в двух местах: в вашем браузере и (если включили синхронизацию) в скрытой папке вашего Google Диска. Промежуточных серверов нет, аналитики нет, cookies нет. Отозвать доступ можно в настройках Google-аккаунта - приложение при этом продолжит работать локально.

Единственное исключение - оценка калорий: по нажатию «≈» текст записи уходит в OpenRouter на вашем же ключе. Не нужно - не вписывайте ключ, кнопки просто не будет.

Не залогинившись, получаете то же самое минус синхронизация - для дневника «на неделю для врача» и этого хватает.

Под капотом

React 19 + TypeScript + Vite, vite-plugin-pwa (Workbox), IndexedDB через idb. Логика слияния, подсказки и разбор ответа модели - чистые функции, покрытые тестами через node --test без Jest. Деплой - GitHub Actions на поддомен, public/CNAME в артефакте.

Исходники открыты: github.com/william-aqn/eat. Форкается в свой вариант за вечер: словарь нового языка - один файл, замена облака - один адаптер (RemoteStore), палитра - CSS-переменные в :root.

Ссылки

tags: pwa - react - indexeddb - google-drive - openrouter - local-first