Skip to content

Latest commit

 

History

39 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Gym Bro

Приложение для тренировок в зале: карта мышц по недельному объёму, каталог упражнений на данных ЭМГ-исследований, конструктор программ и помощник, работающий с состоянием приложения.

Монорепозиторий: сайт на Next.js, мобильное приложение на Expo, общая модель данных.

Демо: https://derfi228.github.io/gym-bro/

Возможности

Карта мышц

  • 13 групп, проекции спереди и сзади, переключение кнопкой
  • Анатомические контуры, левая и правая стороны отрисованы раздельно
  • Каждая группа обведена контуром, выбранная выделяется ярче и толще
  • Заливка поднимается снизу вверх и показывает набранный объём относительно рабочего диапазона, высота меняется анимацией
  • Цвет заливки — статус группы: не затронута, мало, в диапазоне, много, перебор
  • Перебор объёма дополнительно отмечается пунктирным контуром
  • Наведение показывает подходы и статус, клик открывает упражнения группы

Каталог упражнений

  • 62 упражнения, фильтр по группам, поиск по названию, пресет «Свой вес»
  • Сортировка по активации, сложности или алфавиту
  • Карточка упражнения: техника выполнения, разбор вовлечённых мышц с процентами
  • В карточке видно, в каких программах упражнение уже стоит, и можно добавить его в свою тренировку
  • Вовлечённые мышцы подсвечиваются на карте, яркость соответствует вкладу
  • Поля пикового и рабочего веса с шагом 1 кг, расчёт рабочего в процентах от пикового

Программы

  • Разделы: пользовательские тренировки, известные методики, разовые тренировки
  • Всё, что заложено в приложение, подписано авторством GymBro
  • Заготовки GymBro частично изменяемые: упражнение меняется на другое для той же группы, остальное — только в копии
  • Свои тренировки редактируются целиком, дублируются и удаляются
  • Сбор тренировки вручную: каталог открывается в режиме выбора, отмеченные упражнения складываются в программу с собственным названием
  • Конструктор под произвольную длительность от 10 до 120 минут
  • Отстающие группы ставятся первыми, перебранные пропускаются
  • Схема тела рядом со списком: пересчитывается на каждое изменение, по умолчанию показывает саму тренировку, переключается на неделю целиком
  • Подходы меняются шагом в строке упражнения, упражнения переставляются и убираются
  • Добавление нового упражнения от группы, с недельным диапазоном перед списком
  • Блок «Что поправить»: пропущенные и перегруженные группы, правка одной кнопкой
  • Замена упражнения на альтернативу для той же группы
  • Таймер отдыха с обратным отсчётом
  • Справочник методик: фулбоди, верх/низ, PPL, GVT, сплит по группам, Max-OT — с оценкой соответствия данным о частоте и объёме
  • У методики раскрываются тренировочные дни со списком упражнений, каждое открывается в своей карточке
  • Упражнение внутри дня методики заменяется на другое для той же группы, замена помечается и откатывается

GymBro

  • Читает недельный объём: отвечает, что отстаёт и где перебор
  • Собирает программу под запрос и открывает её во вкладке «Программа»
  • Исключает группу из подбора при травме

Режим тренировки

  • Запускается из программы или дня методики, идёт в первой вкладке
  • Отсчёт «3-2-1» со звуковым сигналом перед первым подходом
  • Текущее упражнение, номер подхода и рекомендуемый вес
  • Ответ после подхода — легко / в самый раз / тяжело / не доделал — двигает рабочий вес на ±2.5 кг
  • Таймер отдыха, пауза без потери времени, время тренировки в шапке
  • Выход через подтверждение, в конце — итог с конфетти

Челленджи

  • Недельные задачи с прогрессом, совместные с другом, счётчик серии

Связь между разделами Подход засчитывается целевой мышце полностью, ощутимо вовлечённой — наполовину; так считается и записанная тренировка, и объём собираемой программы. Карта мышц обновляется сразу.

Стек

Слой Технологии
Сайт Next.js 16 (App Router), TypeScript, Tailwind CSS v4
Приложение Expo, React Native, TypeScript
Бэкенд Supabase — вход по почте и база; ключ проекта задаётся при сборке
Хостинг GitHub Pages, статический экспорт

Структура

gym-bro/
├── web/                     сайт
│   └── src/
│       ├── app/             page.tsx — оболочка, globals.css — дизайн-система
│       │                     icon.svg — значок, из него собраны остальные
│       ├── components/
│       │   ├── MuscleFigure.tsx    карта мышц, обе проекции
│       │   ├── BodyPanel.tsx       фигура + легенда + список групп
│       │   ├── ExerciseDetail.tsx  карточка упражнения
│       │   ├── SessionMode.tsx     режим идущей тренировки
│       │   ├── Account.tsx         вход, регистрация, закрытые разделы
│       │   ├── Splash.tsx, icons.tsx
│       │   └── tabs/        BodyTab, ExercisesTab, ProgramTab, GymBroTab, ChallengesTab
│       └── lib/
│           ├── demo.ts      каталог упражнений и методик
│           ├── bodyPaths.ts контуры мышц, сгенерированы
│           ├── landmarks.ts недельный объём: MEV / MAV / MRV, статусы групп
│           ├── volume.ts    объём программы, разбор дисбалансов, правки
│           ├── auth.tsx     аккаунт: сессия и профиль
│           ├── profile.ts  профиль: перевод, рамки, склонения (+ тест)
│           ├── sync.ts     перевод таблиц в состояние и обратно (+ тест)
│           ├── supabase.ts  клиент базы, null пока проект не задан
│           ├── build.ts     сборка программы под время (+ build.test.ts)
│           ├── session.ts   ход тренировки, веса (+ session.test.ts)
│           └── store.tsx    состояние приложения
├── supabase/
│   ├── migrations/          схема базы и правила доступа
│   └── functions/gymbro/    серверная часть помощника (Deno)
├── mobile/                  приложение на Expo
├── shared/types.ts          общая модель данных
└── .github/workflows/       deploy.yml

Запуск

Требуется Node.js 22+.

git clone https://github.com/derfi228/gym-bro.git

Сайт на http://localhost:3000:

cd web && npm install && npm run dev

Мобильное приложение:

cd mobile && npm install && npx expo start

Разработка

  • Модель данных живёт в shared/types.ts, в web импортируется как @shared/types (алиас в web/tsconfig.json)
  • Стили: Tailwind в web, StyleSheet в mobile
  • Палитра и компоненты описаны в design-system.md, но он отражает первую версию. Актуальные отличия: текст набран Manrope вместо системного стека, тёплый янтарь стал вторым цветом (усилие и действие против холодного — данные), фон многослойный с дрейфующим свечением
  • Контуры тела лежат в web/src/lib/bodyPaths.ts — файл сгенерирован из react-native-body-highlighter, правится только перегенерацией
  • Шесть групп в нём слиты из отдельных брюшек в цельные мышцы и переведены в кривые: предплечья и квадрицепс спереди, предплечья, трицепс, спина, бицепс бедра и икры сзади. Икры и трицепс спереди оставлены исходными

Формат данных упражнения

{
  id, name, primary, secondary,
  involvement: { quads: 1, glutes: 0.8 },   // вклад группы 0..1, для подсветки
  description,                               // техника выполнения
  emgPercent: 100 | null,                    // % активации из исследования
  rank: 1,                                   // место в рейтинге, если процентов нет
  evidence: "emg" | "ranking" | "estimate",
  sourceId, difficulty, equipment, note
}

evidence обязателен при добавлении упражнения:

  • emg — процент активации целевой мышцы относительно лучшего упражнения того же исследования
  • ranking — исследование опубликовало порядок без процентов
  • estimate — сравнительного исследования нет, значение экспертное

Источники данных

Группа Исследование
Грудь ACE / UW–La Crosse, 2012
Трицепс ACE, 2011
Бицепс ACE, 2014
Плечи ACE, 2014
Пресс ACE / San Diego State, 2001 — % активности прямой мышцы от классических скручиваний
Спина Edelburg, UW–La Crosse — таблица 4, средний %MVC широчайшей
Трапеции JOSPT, 2003
Ягодицы Contreras и др., 2015, хроническое сравнение
Бицепс бедра J Appl Physiol, 2025, PMC5765783
Икры Kinoshita и др., 2023

Оценка методик опирается на:

Контуры фигуры — из react-native-body-highlighter (MIT, © 2022 ELABBASSI Hicham), переразмечены под наши группы. Условия лицензии в web/src/lib/NOTICE-body-highlighter.txt — файл не удалять.

Что означает число

emgPercent — процент от лучшего упражнения того же исследования для той же мышцы. Нормировка своя у каждой группы, поэтому 100 у жима лёжа и 100 у подтягиваний — это разные эксперименты, а не общая шкала.

Проценты есть у 26 упражнений из 62: грудь, трицепс, спина и пресс. У остальных в интерфейсе честно написано «нет данных» — их исследования либо опубликовали только порядок без чисел (бицепс и плечи у ACE выложены графиком), либо сравнительного исследования под наш набор упражнений не нашлось. Числа из рангов не выводим: место внутри одного исследования не переводится в проценты.

Ограничение метода: ЭМГ измеряет активацию мышцы в моменте и не является подтверждённым предиктором гипертрофии — Vigotsky и др., Sports Medicine 2022. Значения в каталоге сравнивают упражнения между собой, а не прогнозируют рост.

Недельный объём по группам

Границы MEV / MAV / MRV лежат в web/src/lib/landmarks.ts — одна таблица на 13 групп, от неё считаются заливка тела, статусы и правки к программе.

Ориентир Что означает
MEV минимум, ниже которого прироста практически нет
MAV рабочий диапазон
MRV потолок, выше которого восстановление не успевает
  • Нижняя граница: Schoenfeld, Ogborn, Krieger, 2017 — дозозависимая связь объёма и гипертрофии, рост до ~10 подходов в неделю
  • Подтверждение: Baz-Valle и др., 2022 — систематический обзор объёма
  • Сами значения landmarks — Israetel и др., «Scientific Principles of Hypertrophy Training». Это модель планирования, а не мета-анализ: у групп с basis: "model" цифра держится только на ней
  • Деление недельного диапазона пополам на одну тренировку — из данных о частоте: Schoenfeld и др., 2016

Ограничение: индивидуальная переносимость объёма различается в разы, поэтому в интерфейсе это «диапазон», а не «норма».

База данных и аккаунты

Без аккаунта открыт каталог упражнений. Всё, что считается от человека — схема тела, свои тренировки, помощник, челленджи — просит войти.

Если ключи проекта в сборку не переданы, вход выключен целиком и приложение работает как раньше, без замков. Это специально: сайт не должен ломаться от того, что база ещё не подключена.

Что где лежит

Таблица Что хранит
profiles пол, рост, вес, год рождения, уровень — 1:1 с аккаунтом
exercise_weights пиковый и рабочий вес по упражнениям
programs свои тренировки, слоты одним полем jsonb
workouts проведённые тренировки
workout_sets каждый выполненный подход отдельной строкой
goals цели и вердикт помощника по ним
chat_messages переписка с помощником, включая вызовы инструментов
memory_facts что помощник запомнил про человека

Челленджей и подписок в базе нет: этих функций пока не существует, таблицы под них были бы пустыми. Недельный объём по группам мышц не хранится — он считается из workout_sets, чтобы не разъезжаться с ними.

Что уже ездит в базу

Данные Когда пишется
Профиль по кнопке «Сохранить»
Веса по упражнениям через 0,8 с после последней правки поля
Свои тренировки через 0,8 с после изменения, удаление — сразу
Проведённые тренировки строка при старте, отметка времени в конце
Выполненные подходы каждый подход отдельной строкой

Недельный объём на схеме тела считается из подходов за текущую неделю (с понедельника). Пока человек не вошёл, показываются демонстрационные цифры из demo.ts — иначе первый экран был бы пустым.

При входе и выходе состояние приложения пересоздаётся целиком: провайдер хранилища получает ключ по идентификатору аккаунта. Чужие веса не доживают до следующего человека, и чистить их вручную не нужно.

Доступ закрыт построчно (RLS) на стороне базы: человек видит только свои строки. Это принципиально — ключ, с которым ходит сайт, публичный и лежит в браузере у каждого. Он ничего не защищает и защищать не должен.

Год рождения хранится вместо возраста: возраст, записанный однажды, через год станет неверным.

Первая настройка

  1. На supabase.com создайте аккаунт и проект. Регион — ближайший (Frankfurt). Пароль от базы сохраните, он больше нигде не показывается.
  2. В проекте откройте SQL Editor, вставьте целиком supabase/migrations/20260823120000_init.sql и нажмите Run.
  3. Project Settings → API: скопируйте Project URL и ключ anon public.
  4. Для локальной разработки: скопируйте web/.env.example в web/.env.local и подставьте оба значения.
  5. Для сайта: в GitHub репозитории Settings → Secrets and variables → Actions заведите переменную NEXT_PUBLIC_SUPABASE_URL (вкладка Variables) и секрет NEXT_PUBLIC_SUPABASE_ANON_KEY (вкладка Secrets). Без них сборка пройдёт, но вход останется выключенным.
  6. Authentication → URL Configuration: в Site URL и Redirect URLs добавьте адрес сайта — иначе ссылка из письма подтверждения приведёт не туда.

Служебный ключ service_role в сайт не попадает никогда: он обходит все правила доступа. Его место — только в серверных функциях Supabase, там же будет жить ключ от модели.

Безопасность

Что проверено и на чём это держится:

Что Как закрыто
Чужие данные в базе RLS на всех таблицах, проверено запросом без входа: чтение отдаёт пусто, запись отбивается
Страница входа отдаётся только по HTTPS, обращения по http перекидываются
Ключ модели лежит в переменных серверной функции, в сборку сайта не попадает
Кто может звать модель функция читает роль из токена и пускает только authenticated; публичный ключ сайта отбивается
Размер запроса к модели ограничены и число сообщений, и объём текста — иначе один запрос стоил бы как тысяча
Что модель может сделать инструменты выполняет приложение; названия групп и упражнений сверяются с каталогом, время загоняется в рамки

Публичный ключ Supabase виден в коде страницы — так и задумано, данные защищает не он, а RLS. Служебный ключ service_role не должен попадать никуда, кроме переменных серверных функций.

Остаётся: вошедший человек может слать вопросы часто и тратить деньги владельца ключа. Ограничения по частоте нет — для двоих это неважно, при посторонних пользователях понадобится.

Помощник

Ключ от модели лежит в серверной функции Supabase и в браузер не попадает — сайт статический, спрятать его там негде. Функция делает ровно три вещи: проверяет, что человек вошёл, подставляет его данные в наставление и зовёт модель.

Разбора данных на сервере нет намеренно. Каталог упражнений, расчёт объёма и сборка программ живут в приложении; продублировать их на Deno означало бы держать две копии, которые разойдутся. Поэтому приложение присылает готовый срез своих данных, а действия, которые просит модель, выполняет само.

Модель: deepseek-v4-flash. Около $0.44 за миллион входных и $1.32 за миллион выходных — один вопрос обходится примерно в $0.0015. deepseek-v4-pro втрое дороже, брать его есть смысл, только если flash перестанет справляться.

Что модель умеет вызвать (выполняется в приложении, не на сервере):

Действие Что делает
build_program собирает тренировку под время и открывает её
show_on_body открывает группу мышц на схеме тела
exercise_history читает последние подходы в упражнении из базы
remember записывает факт о человеке в память

Переписка хранится в базе, модели уходит хвост в 20 сообщений. Разорванные пары «вызов инструмента — его результат» отбрасываются до отправки: срез приходится на середину, а поставщик такую ленту отвергает целиком.

Память — отдельная таблица коротких фраз. Они подставляются в каждый разговор, поэтому их мало и они ограничены по длине. На подбор тренировок память не влияет: приложение собирает их своими правилами, а помощник только подсказывает, что факт мешает. Иначе было бы непонятно, почему из программы пропали приседы.

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

Первая настройка

  1. Возьмите ключ на platform.deepseek.com (или чужой, если им поделились).

  2. Положите ключ в переменные функции — он не должен попасть в репозиторий:

    npx supabase secrets set AI_API_KEY=ваш-ключ --project-ref ecoxikheivexznsszjgf

    Либо в панели: Edge Functions → Secrets → Add new secret.

    Адрес и модель тоже читаются из переменных, но у них есть значения по умолчанию — для ключа DeepSeek задавать ничего не нужно. Ключ OpenRouter (начинается на sk-or-v1-) отличается только этим:

    Переменная Значение
    AI_BASE_URL https://openrouter.ai/api/v1/chat/completions
    AI_MODEL deepseek/deepseek-v4-flash-0731

    Через OpenRouter та же модель выходит примерно вдесятеро дешевле: $0.04 против $0.44 за миллион входных.

  3. Выложите функцию:

    npx supabase functions deploy gymbro --project-ref ecoxikheivexznsszjgf

    Либо в панели: Edge Functions → Deploy a new function, вставить содержимое supabase/functions/gymbro/index.ts.

Готовые вопросы во вкладке считаются на месте и работают без модели — они не сломаются, если ключ кончится.

Значок

Источник — web/src/app/icon.svg. Остальные форматы собираются из него:

cd web && node make-icons.mjs

Скрипт кладёт favicon.ico (16, 32, 48 — для вкладки и Windows) и apple-icon.png 180×180 (для экрана телефона, iOS не понимает svg). Формат .ico — контейнер вокруг нескольких PNG, поэтому собирается вручную: sharp его не пишет. Сам sharp приходит вместе с Next, отдельно ставить не нужно.

Рисунок должен читаться в 16 пикселей: тонкие штрихи и мелкие детали там превращаются в кляксу, проверять надо на реальном размере.

Деплой

Пуш в main с изменениями в web/, shared/ или workflow запускает сборку статики и публикацию на GitHub Pages.

Сайт живёт в корне gym-bro.ru, поэтому базовый путь пустой, а привязка домена лежит в web/public/CNAME и попадает в сборку — иначе она слетала бы при каждой публикации.

Домен ведёт на GitHub Pages четырьмя A-записями в зоне у регистратора: 185.199.108.153, 109, 110, 111 (последний октет .153 у всех). У www те же адреса, GitHub сам перекидывает его на голый домен.

Сайт работает только по HTTPS: сертификат выпущен на gym-bro.ru и www.gym-bro.ru, обращения по http перекидываются на https. Сертификат продлевается сам.

Если домен когда-нибудь отключат, в deploy.yml надо вернуть NEXT_PUBLIC_BASE_PATH=/gym-bro, иначе сайт в подпапке не найдёт свои стили и скрипты.

Если GitHub залипнет на «DNS Check in Progress». Так бывает: записи уже верные, а проверка помнит старый ответ и сертификат не заказывает. Лечится пересозданием привязки — в Settings → Pages снять домен, сохранить, вписать обратно. Проверка запускается заново, и сертификат выпускается за минуту.

gh run list --limit 3

Проверки

Чистая логика — ход тренировки и сборка программы — проверяется штатным раннером Node, без тестового фреймворка:

cd web && npm test

Перед коммитом стоит прогнать всё:

cd web && npm test && npx tsc --noEmit && npm run lint && npm run format:check

Форматирование единое, его держит Prettier:

cd web && npm run format

Сгенерированный bodyPaths.ts из форматирования исключён — его формат всё равно теряется при следующей перегенерации контуров.

Node не достраивает расширения в относительных TS-импортах, поэтому запуск идёт через хук web/ts-resolve.mjs. Он нужен только тестам: Next и tsc резолвят сами.

Если сборка висит в очереди и не стартует, её держит предыдущий запуск через concurrency: pages. Запустить заново:

gh workflow run deploy.yml --ref main

Разбудить базу вручную, если проект всё-таки уснул:

gh workflow run keepalive.yml --ref main

Бэкап

Точки отката:

Метка Что в ней
v1.1, backup/2026-08-22-before-cleanup режим тренировки, оформление, проверенные проценты
v1.0, backup/2026-08-07-stable анатомическая карта мышц, конструктор программ
git reset --hard v1.1

Журнал изменений

2026-08-23 — Оформление доведено до края

Свой значок вместо логотипа Next: тот остался от шаблона и висел на вкладке, в закладках и на экране телефона. Источник — icon.svg, из него собираются favicon.ico и apple-icon.png.

Приведены в порядок элементы, остававшиеся браузерными посреди авторского оформления: ползунок времени (был 16 px и не ловился пальцем, стал 28 со своей дорожкой и бегунком), стрелки у числовых полей, крестик у поля поиска, полоса прокрутки, серая вспышка при касании на телефоне.

Область нажатия у мелких круглых кнопок и пилюль-фильтров растянута невидимо: сами кнопки прежние, но пальцем в них теперь попадаешь. У пилюль растяжка ровно на просвет между рядами — больше нельзя, верхний ряд начал бы перехватывать нажатия нижнего.

Полоса браузера на телефоне красится в цвет фона, его элементы рисуются в тёмном варианте, нижняя панель не прячется под чёрточкой на телефонах без кнопки. Добавлены теги предпросмотра: в переписке видно название и описание, а не голый адрес.

Проверено измерением: горизонтального перелива нет ни на одной вкладке ни при 375, ни при 1280; контраст текста везде проходит порог.

2026-08-23 — Домен переведён на HTTPS

GitHub залип на «DNS Check in Progress»: записи в зоне уже были верными, но проверка помнила старый ответ и сертификат не заказывала, из-за чего Enforce HTTPS был недоступен, а страница входа отдавалась без шифрования. Привязка домена пересоздана, сертификат выпущен на gym-bro.ru и www.gym-bro.ru, Enforce HTTPS включён.

2026-08-23 — Помощник помнит и показывает, чем занят

Переписка переехала в базу: возвращаешься во вкладку — она на месте, и на телефоне та же. Модели отправляется хвост в 20 сообщений.

У помощника появилась память: он сам отмечает долгоиграющее — травму, инвентарь, цель — и говорит об этом в переписке. Запомненное видно сворачиваемым списком, любой пункт удаляется. На подбор тренировок память не влияет, только на разговор.

Пока модель отвечает, видно три точки и подпись, чем именно она занята: «собираю тренировку», «смотрю историю подходов». Раньше пауза читалась как зависание.

У тренировок, собранных помощником, убран разбор «Что поправить». Он считает неделю целиком, а одна тренировка её закрыть не может, поэтому совет «добавьте ещё» выходил всегда — и читался как «исправь то, что я тебе только что дал».

Признак «собрано помощником» стал явным: раньше он выводился из наличия пояснения к программе, и тренировки от помощника им не помечались.

2026-08-23 — Помощник разговаривает с моделью

Вкладка GymBro получила поле для свободного вопроса. Ответ считает deepseek-v4-flash через серверную функцию Supabase — ключ лежит там и в браузер не попадает.

Модель видит профиль и объём за неделю и может попросить приложение собрать тренировку, открыть группу на схеме или показать историю подходов в упражнении. Выполняет всё приложение: программы собирает свой алгоритм, а не модель.

Названия групп и упражнений, которых нет в каталоге, отбрасываются — модель не может подставить вместо них похожие. Испорченные аргументы вызова не роняют разговор, а возвращаются модели сообщением об ошибке.

Готовые вопросы остались и считаются на месте: они работают, даже когда модель недоступна.

Выбор группы на схеме тела переехал в общее состояние — иначе «покажи на схеме» переключало вкладку, но ничего не показывало.

Исправлен хук запуска тестов: он дописывал «.ts» к путям внутри зависимостей, из-за чего падал любой тест, который тянет клиент Supabase.

2026-08-23 — Сайт собирается под свой домен

Домен gym-bro.ru уже был прописан в настройках GitHub Pages, из-за чего старый адрес derfi228.github.io/gym-bro стал перекидывать на него. Сборка при этом продолжала считать, что живёт в подпапке.

Добавлен web/public/CNAME (иначе привязка слетает при каждой публикации), базовый путь убран. Проверено на собранных файлах: ссылок на /gym-bro/ не осталось, пути идут от корня.

2026-08-23 — Приложение поехало в базу

Ключи проекта прописаны в репозитории (vars и secrets), поэтому вход работает и на живом сайте, а не только локально.

В базу пишутся веса по упражнениям, свои тренировки, проведённые тренировки и каждый выполненный подход. Веса и программы уходят с задержкой 0,8 с: поле веса правится на каждое нажатие клавиши, и без задержки каждое нажатие было бы запросом.

Недельный объём на схеме тела теперь считается из записанных подходов, а не берётся из зашитых цифр. Демонстрационные значения остались для тех, кто не вошёл.

Добавлено задание keepalive.yml: раз в трое суток дёргает базу. Бесплатный проект Supabase останавливается после недели простоя, и сайт переставал бы пускать людей до ручного нажатия в панели.

Исправлено: подход мог попасть в базу дважды. Записи стояли внутри функции обновления состояния, а React вправе вызвать её повторно.

Идентификаторы своих тренировок стали uuid — прежние строки вида prog-<время>-<число> не подходят типу ключа в базе.

Убран resetWeek: он возвращал демонстрационные цифры, что после подключения базы стало просто неверным. Им никто не пользовался.

2026-08-23 — Профиль пишется в базу

Над схемой тела вместо зашитых «182 см / 78 кг / 24 года» — профиль из базы. Полоска раскрывается в форму: рост, вес, год рождения, пол, опыт. Сохранение идёт одним запросом и правит только изменённые поля.

Помощник перестал называть выдуманные цифры: пока профиль пустой, он про него молчит вместо того, чтобы считать «от 182 см».

Перевод между базой и моделью вынесен в web/src/lib/profile.ts — там же рамки значений, повторяющие проверки в самой базе, и склонение возраста («21 год», «24 года», «25 лет»). На всё это есть тесты.

Проверено по живой базе: имена всех колонок, которые пишет код, сверены запросом к схеме — выдуманная колонка отвергается, наши проходят.

Из shared/types.ts убрана функция: псевдоним @shared настроен для TypeScript, но не для сборщика, и значение оттуда роняет сборку. Файл остаётся только с типами, правило записано в CLAUDE.md.

2026-08-23 — Аккаунты и база данных

Подключён Supabase: вход по почте и паролю, шесть таблиц с построчным доступом, профиль заводится триггером вместе с аккаунтом.

Каталог упражнений открыт без регистрации. Схема тела, свои тренировки, помощник и челленджи просят войти. При первом заходе — выбор: создать аккаунт, войти или посмотреть без него; выбор запоминается. Вход и выход — в правой части шапки.

Пока ключи проекта не переданы в сборку, вход выключен целиком и приложение работает как раньше. Проверено сборкой без ключей: замков нет, ключ в файлы сборки не попадает.

В модели профиля вместо возраста хранится год рождения — записанный однажды возраст через год становится неверным.

Не сделано: данные приложения (веса, программы, история тренировок) пока живут в памяти вкладки и в базу не пишутся, помощник к модели не подключён.

2026-08-23 — Сборка программы, поиск по мышцам, замена на тренировке

Поиск в каталоге ищет и по группам мышц. Раньше окно поиска смотрело только в название, поэтому «спина» не находила тяги и подтягивания. Теперь в строку поиска попадают названия целевой и вспомогательных групп.

Минимум тренировки — 30 минут. Поле, ползунок и ориентир у готовой программы начинаются с 30; у помощника кнопка «короткой» тоже стала на 30. Расчётная длительность тренировки, собранной вручную из каталога, по-прежнему считается по её содержимому — это измерение, а не выбор.

Сборка программы переписана и вынесена в web/src/lib/build.ts. Раньше слоты набирались по кругу от самой отстающей группы и в тренировку попадало то, что окажется первым в каталоге. Что изменилось:

  • на каждом шаге берётся упражнение, закрывающее больше всего недобора, а базовые движения идут с множителем — тренировка держится на приседе, тяге и жиме, а не на наборе изоляции;
  • прямая работа весит вдвое против попутной, иначе самая пустая группа (икры) никогда не получала своего упражнения;
  • одно движение не берётся дважды: присед со штангой заведён в каталоге под квадрицепс и под ягодицы, и обе записи попадали в одну тренировку;
  • не больше двух упражнений на группу;
  • порядок выполнения — базовые вперёд, пресс и икры в конец;
  • остаток времени идёт в подходы уже выбранных движений, а не в девятое упражнение: раньше тренировка на 90 минут занимала 42.

Логика вынесена из store.tsx, потому что там она тянула за собой React и не запускалась отдельно. Теперь на неё есть шесть проверок в build.test.ts.

Замена упражнения прямо на тренировке. В карточке текущего упражнения появилась кнопка «Заменить»: список составлен из упражнений на ту же целевую группу. Подходы, повторы и отдых остаются от слота, сделанные подходы не сгорают. Список закрывается сам при переходе к следующему упражнению.

2026-08-22 — Проверенные проценты вместо рангов

Из каталога убраны подписи «№N»: место внутри одного исследования нельзя сравнивать с местом из другого, а в общем списке они стояли рядом.

Для спины и пресса найдены и проставлены измеренные значения: таблица 4 диссертации Edelburg (средний %MVC широчайшей, нормировано к подтягиваниям) и таблица ACE/SDSU 2001 (% активности прямой мышцы, нормировано к велосипеду). Упражнений с проверенным числом стало 26 из 62 вместо 15.

Скручивания на блоке больше не ссылаются на исследование ACE — их там не тестировали.

В карточке появились две шкалы: активация синяя, сложность янтарная. Шкала активации подписана целевой мышцей — под фильтром по вспомогательной группе процент читался как её активация, хотя относится к целевой.

2026-08-20 — Оформление

Текст переведён на Manrope: системный стек спорил с серифом в заголовках. Фон стал многослойным — источники света, тёплый отсвет снизу, зерно, виньетка и медленно дрейфующее свечение. Карточки получили блик по верхней кромке и тень, карточки-кнопки приподнимаются при наведении.

Тёплый цвет повышен до второго: холодный несёт данные, тёплый — действие, поэтому кнопки старта тренировки янтарные. Заголовки разделов набраны серифом с линией до края и делят длинную страницу на главы. Содержимое вкладки появляется лесенкой через :nth-child, без правки разметки.

2026-08-20 — Режим тренировки

Тренировка запускается из программы или дня методики и идёт в первой вкладке: отсчёт со звуком, текущее упражнение с рекомендуемым весом, отметка подхода с оценкой, таймер отдыха, пауза и время тренировки. Оценка подхода двигает рабочий вес, отмеченный подход попадает в недельный объём.

Рекомендуемый вес берётся из рабочего, а при его отсутствии считается из пикового по формуле Эпли. Если не задано ничего — рекомендации нет.

Логика тренировки лежит в web/src/lib/session.ts без React и проверяется через node --test src/lib/session.test.ts — без тестового фреймворка.

Незатронутые мышцы на схеме стали серыми вместо бледно-синих: раньше они читались как слегка задействованные.

2026-08-10 — Свои тренировки, поиск в каталоге, авторство GymBro

В каталоге появились поиск по названию, пресет «Свой вес» и сортировка по алфавиту; все условия складываются. Карточка упражнения показывает, в каких программах оно стоит, и позволяет добавить его в свою тренировку.

Программы разделены на пользовательские, известные методики и разовые тренировки; пользовательские идут первыми. Всё встроенное подписано автором GymBro и переведено в режим частичной правки: упражнение можно заменить на другое для той же группы, включая упражнения внутри дней методик. Свои тренировки редактируются целиком, дублируются и удаляются.

Добавлен ручной сбор тренировки: каталог открывается в режиме выбора с полем названия, крестиком для сброса и галочкой для подтверждения. Место под оценку модели заведено (Program.aiScore), но пока показывает прочерк — модели нет, числа не выдумываются.

2026-08-08 — Мышцы слиты в цельные фигуры

Шесть групп состояли из нескольких брюшек с зазорами и читались как нарезка. Дольки слиты морфологическим замыканием, контур переразмечен и переведён в кривые Безье со сглаживанием Тобина — оно не съедает объём мышцы. Затронуты предплечья и квадрицепс спереди, предплечья, трицепс, спина, бицепс бедра и икры сзади. Икры и трицепс спереди не менялись: там по одному пути на сторону.

2026-08-07 — Мышцы читаются волокнами

Убрана разделительная обводка цветом фона между путями — она резала мышцу на плитки. Контуры переведены на non-scaling-stroke: толщина задаётся в пикселях экрана и не съедается уменьшением фигуры. Сама фигура увеличена до 330 px. Выделяется только выбранная группа.

2026-08-07 — Контур тела под мышцами

Под группы добавлен BODY_OUTLINE — силуэт целиком, залитый фоном. Раньше промежутки между группами показывали фон карточки, и вид сзади, где почти все группы отстают, читался как набор фрагментов.

2026-08-07 — Анатомическая фигура

Нарисованные вручную контуры заменены на геометрию из react-native-body-highlighter (MIT): 13 групп покрыты полностью, пресс разбит по сегментам, левая и правая стороны заданы отдельно — зеркалирование убрано. Пути вынесены в bodyPaths.ts, логика заливки, статусов и кликов не менялась.

2026-08-06 — Конструктор с живой картой мышц

Объём вынесен в landmarks.ts: MEV / MAV / MRV на 13 групп, целевые цифры больше не захардкожены в загрузке. Схема тела получила заливку по статусу — не затронута, мало, в диапазоне, много, перебор — и плавную анимацию высоты. Вкладка «Программа» стала конструктором: подходы меняются шагом прямо в списке, упражнения переставляются и убираются, добавление идёт от группы. Тело рядом пересчитывается на каждое изменение, переключается между «только тренировка» и «неделя целиком». Блок «Что поправить» показывает пропущенные и перегруженные группы и применяет правку одной кнопкой. Ползунок времени больше не режет программу — только показывает расхождение с ориентиром.

2026-08-05 — Дни методик и контуры мышц

Методики раскрываются в список тренировочных дней с упражнениями; упражнение открывается в карточке. Шаг полей веса — 1 кг. Мышцы на карте получили отдельные контуры, выбранная группа выделяется толщиной и цветом.

2026-08-05 — Карта мышц на 13 групп

Подключена схема с проекциями спереди и сзади, добавлены спина, ягодицы и бицепс бедра. Каталог расширен до 62 упражнений с описанием техники и картой вовлечённости. Упражнения открываются в карточке с подсветкой мышц и полями пикового и рабочего веса. В программы добавлен справочник методик. Из вкладки «Упражнения» убраны подписи источников.

2026-08-04 20:06 — Трицепс и данные исследований 6e68107

Добавлена группа «трицепс», фигура перерисована. Каталог переведён на данные ЭМГ-исследований с полем evidence. Появился общий стор: отметка подхода заполняет карту мышц, GymBro читает объём и создаёт программы. Вкладка «Программа» разбита на три уровня, длительность задаётся произвольно.

2026-08-04 19:19 — Формат приложения 2663889

Заставка при входе, нижняя панель на пять вкладок вместо прокрутки. Вкладки стали интерактивными.

2026-08-04 18:59 — Сайт и автодеплой 2d7b574

Первая версия сайта, модель данных в shared/types.ts, статический экспорт и workflow для GitHub Pages.

2026-08-04 17:44 — Инициализация b09263c

Созданы web/, mobile/, shared/.

Планы

  • Подключить Supabase: авторизация, база, storage
  • Экраны мобильного приложения — сейчас шаблон Expo
  • Языковая модель для GymBro вместо локальной логики подбора
  • Расчёт целевого объёма по росту, весу и возрасту — сейчас общий для всех
  • Исследования по трапециям, предплечьям и косым — часть значений estimate
  • Построение карты по фото пользователя
  • Сохранение данных: состояние живёт в памяти вкладки
  • Landmarks с basis: "model" — найти исследования вместо ориентиров

About

Приложение для зала: карта мышц по недельному объёму, каталог упражнений на данных ЭМГ-исследований, конструктор программ. Монорепозиторий Next.js + Expo.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages