av 1a5aa56379 research: bun вместо node для тулчейна фронта — не переходим
- под рантаймом bun vue-tsc не видит .vue: подмена fs.readFileSync не перехватывает загрузчик модулей
- остальное (vite, vitest, biome, audit) работает, но прироста скорости на нашем объёме нет
- записаны обходные пути, цена переезда и условия, при которых вернуться к вопросу
2026-10-07 14:04:00 +03:00

Дневник питания

Личный трекер питания: записал текстом или снимком — сервер разобрал
запись на позиции и посчитал калории, белки, жиры и углеводы.

Что это

Self-hosted приложение для контроля питания на своём сервере: один бинарь на Go, одна база SQLite, клиент — PWA внутри того же бинаря. Отдельная причина, по которой проект вообще существует: прошлый трекер стал недоступен в стране, и повторения не хочется — система должна принадлежать своему хозяину целиком.

Запись — это событие с сырым содержимым: строка «каша овсяная на молоке 300 г, кешью 15 г», снимок тарелки, время. Клиент только фиксирует его и кладёт в очередь; разбор на позиции и подсчёт КБЖУ идут на сервере асинхронно. На экране — дневник дня, карточка приёма с позициями и остаток дневной нормы.

Пользователей у системы двое, и оба равноправны. Человек записывает еду, смотрит остаток нормы и правит неточности. AI-ассистент раз в неделю читает данные, обсуждает их с человеком и обновляет нормы: у питания есть своя половина картины, а сон, активность и анализы ассистент уже видит. Поэтому отдача данных наружу — не «экспорт как дополнительная фича», а вторая точка входа, под которую спроектирована модель данных.

Принципы

  • Слушаем, но не осуждаем. Приложение фиксирует, а не морализирует. Факты показывать можно — «четвёртый подход сегодня», «сахар за день», — оценку фактам давать нельзя. Отсюда и палитра: у перебора нормы нет цвета тревоги, а единственный красный достаётся необратимому действию, но никогда еде.
  • Сама запись и есть вмешательство. Необходимость залогировать конфету останавливает от её поедания. Следствие неочевидное: делать запись сладостей незаметной вредно. Скорость — там, где нет проблемы, осознанность — там, где она есть.
  • У каждого числа есть источник. Измерено на весах, взято из каталога, оценено моделью, введено человеком. Ассистент должен понимать, спорит он с измерением или с догадкой.
  • Прошлое не переписывается молча. Правка задним числом нормальна, но позиция журнала хранит собственный снимок названия и цифр: изменение каталога не трогает уже сделанные записи.

Чего приложение сознательно не делает: не соцсеть, не медицинский дневник, не тренер и не считает сожжённые калории. Остаток = норма − съеденное. Подробнее — в docs/product.md.

Как устроено

  • Захват на клиенте, обработка на сервере. Клиенту не нужны ни модель, ни разбор ввода, и чисел он не считает: любой ввод, в том числе прилинкованный продукт каталога, уходит на разбор на сервер.
  • Offline-first. Источник правды до отправки — локальная очередь на устройстве. Как появилась сеть, сырые данные уезжают на сервер; опорный сценарий — поезд: в пути ввёл текст и фото, на станции синхронизировал.
  • Обычный REST вместо своего протокола синхронизации. Очередь проигрывает отложенные запросы по порядку, от дублей защищают ULID с клиента, при разошедшихся правках побеждает последний PUT. Обратной очереди нет: сервер шлёт маркеры изменений, клиент перечитывает день целиком.
  • PWA — целевой вид приложения. Нативные обёртки и TestFlight отвергнуты, ограничения iOS-браузера принимаются и обходятся.

Подробнее — в docs/architecture.md и docs/pipeline.md.

Стек

Часть Чем сделано
Сервер Go, SQLite; один бинарь, внутрь него вшит собранный клиент
Клиент TypeScript, Vue, Vuetify, Dexie для локальной базы; SPA + PWA
Вход людей Authelia перед приложением, личность приходит заголовками
Вход машин долгоживущие токены: ассистент и скрипты через forward-auth не пройдут
AI Bifrost как OpenAI-совместимый шлюз, за ним сменные провайдеры
Тулинг Task, golangci-lint, Biome, lefthook, гейт на Python

Что в репозитории

Путь Что там
cmd/diet точка входа сервера
internal разбор приёмов, хранилища, HTTP API, воркер, вход, конфигурация
frontend клиент на Vue; собранная сборка вшивается в бинарь
docs решения и объяснения «почему так», конвенции, ADR, исследования
tasks что делаем дальше: задача на документ, по убыванию приоритета
scripts гейт, проверка инструментов, запуск Node в контейнере

Запуск локально

Нужны Go (версия — в go.mod), Python 3, Docker (в нём собирается фронт) и Task. Что именно и каких версий — в tools.json, проверяется командой task setup.

task setup                          # инструменты на месте?
cp config.example.toml config.toml  # и поправить пути под себя
task front:install && task front:build
task run

В конфиге обязательного немного: адрес прослушивания, путь к базе, каталоги файлов и кеша снимков. Оба шага разбора умеют работать заглушкой (type = "stub" в [llm.*]) — приложение поднимается и живёт без ключей к модели. Для запуска по HTTP без TLS нужен auth.secure_cookie = false, иначе браузер не сохранит сессионную куку. Личность приложение берёт из заголовка Remote-User, так что перед ним в любом случае стоит прокси, который этот заголовок ставит, — в рабочем контуре это Authelia.

Перед коммитом — task gate: все проверки в фиксированном порядке, до конца, со сводкой в конце. Код 0 — сошлось, 1 — расхождение, 3 — в системе нет инструмента и проверка не выполнялась.

Развёртывание за периметром — Caddy, Authelia, бэкапы — описано в docs/deployment.md.

Подключение ассистента

Второй пользователь системы ходит за данными по MCP — эндпоинт отдаёт сам сервер, отдельный процесс-мост ставить не нужно.

Сначала человек выпускает себе личный токен: в приложении Настройки → Токены доступа → Создать. Текст токена показывается один раз, при выпуске, — потом его можно только отозвать и выпустить новый.

claude mcp add --transport http --scope user diet \
  https://<адрес приложения>/api/v1/mcp \
  --header "Authorization: Bearer <токен>"

Область user, а не local: ассистент разбирает неделю рядом с медицинской картой, а не в этом репозитории, и сервер должен быть виден ему отовсюду. Проверить, что подключилось, — командой /mcp в Claude Code; там же видно четыре инструмента: приёмы пищи за период, один приём, чтение нормы и её запись.

Отдаёт эндпоинт ровно то же, что обычное API, и ограничен данными владельца токена. Журнал ассистент только читает: инструмент ввода еды — само приложение. Что именно доступно и почему — в docs/assistant-api.md.

Куда смотреть дальше

Документ О чём
docs/README.md карта всех решений: продукт, сценарии, модель данных, API, интерфейс
docs/conventions/README.md правила, общие для всего кода, утверждениями с номерами
tasks/README.md список работ: цель, описание, критерии приёмки, цена бездействия
docs/history.md летопись: что сделано, что отменили и почему
CLAUDE.md как ведётся работа над проектом и что известно о его хозяине
S
Description
No description provided
Readme
6 MiB
0 Stars 1 Watchers 0 Forks
Languages
Go 64.6%
TypeScript 23.9%
Vue 10.1%
Python 0.8%
CSS 0.3%
Other 0.2%