Приложение для тренировок в зале: карта мышц по недельному объёму, каталог упражнений на данных ЭМГ-исследований, конструктор программ и помощник, работающий с состоянием приложения.
Монорепозиторий: сайт на 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 |
Оценка методик опирается на:
- Объём: Schoenfeld и др., 2017, мета-регрессия
- Частота: Schoenfeld и др., 2016, волюм-эквивалентный анализ, 2019
- Отдых между подходами: Schoenfeld и др., 2016
- GVT: Amirthalingam и др., 2017
Контуры фигуры — из
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) на стороне базы: человек видит только свои строки. Это принципиально — ключ, с которым ходит сайт, публичный и лежит в браузере у каждого. Он ничего не защищает и защищать не должен.
Год рождения хранится вместо возраста: возраст, записанный однажды, через год станет неверным.
- На supabase.com создайте аккаунт и проект. Регион — ближайший (Frankfurt). Пароль от базы сохраните, он больше нигде не показывается.
- В проекте откройте SQL Editor, вставьте целиком
supabase/migrations/20260823120000_init.sqlи нажмите Run. - Project Settings → API: скопируйте
Project URLи ключanon public. - Для локальной разработки: скопируйте
web/.env.exampleвweb/.env.localи подставьте оба значения. - Для сайта: в GitHub репозитории Settings → Secrets and variables →
Actions заведите переменную
NEXT_PUBLIC_SUPABASE_URL(вкладка Variables) и секретNEXT_PUBLIC_SUPABASE_ANON_KEY(вкладка Secrets). Без них сборка пройдёт, но вход останется выключенным. - 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 сообщений. Разорванные пары «вызов инструмента — его результат» отбрасываются до отправки: срез приходится на середину, а поставщик такую ленту отвергает целиком.
Память — отдельная таблица коротких фраз. Они подставляются в каждый разговор, поэтому их мало и они ограничены по длине. На подбор тренировок память не влияет: приложение собирает их своими правилами, а помощник только подсказывает, что факт мешает. Иначе было бы непонятно, почему из программы пропали приседы.
Программы собирает приложение своим алгоритмом, а не модель — числа она не выдумывает. Названия групп и упражнений, которых нет в каталоге, отбрасываются, а не подставляются похожими.
-
Возьмите ключ на
platform.deepseek.com(или чужой, если им поделились). -
Положите ключ в переменные функции — он не должен попасть в репозиторий:
npx supabase secrets set AI_API_KEY=ваш-ключ --project-ref ecoxikheivexznsszjgfЛибо в панели: Edge Functions → Secrets → Add new secret.
Адрес и модель тоже читаются из переменных, но у них есть значения по умолчанию — для ключа DeepSeek задавать ничего не нужно. Ключ OpenRouter (начинается на
sk-or-v1-) отличается только этим:Переменная Значение AI_BASE_URLhttps://openrouter.ai/api/v1/chat/completionsAI_MODELdeepseek/deepseek-v4-flash-0731Через OpenRouter та же модель выходит примерно вдесятеро дешевле: $0.04 против $0.44 за миллион входных.
-
Выложите функцию:
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Свой значок вместо логотипа Next: тот остался от шаблона и висел на вкладке,
в закладках и на экране телефона. Источник — icon.svg, из него собираются
favicon.ico и apple-icon.png.
Приведены в порядок элементы, остававшиеся браузерными посреди авторского оформления: ползунок времени (был 16 px и не ловился пальцем, стал 28 со своей дорожкой и бегунком), стрелки у числовых полей, крестик у поля поиска, полоса прокрутки, серая вспышка при касании на телефоне.
Область нажатия у мелких круглых кнопок и пилюль-фильтров растянута невидимо: сами кнопки прежние, но пальцем в них теперь попадаешь. У пилюль растяжка ровно на просвет между рядами — больше нельзя, верхний ряд начал бы перехватывать нажатия нижнего.
Полоса браузера на телефоне красится в цвет фона, его элементы рисуются в тёмном варианте, нижняя панель не прячется под чёрточкой на телефонах без кнопки. Добавлены теги предпросмотра: в переписке видно название и описание, а не голый адрес.
Проверено измерением: горизонтального перелива нет ни на одной вкладке ни при 375, ни при 1280; контраст текста везде проходит порог.
GitHub залип на «DNS Check in Progress»: записи в зоне уже были верными, но
проверка помнила старый ответ и сертификат не заказывала, из-за чего Enforce
HTTPS был недоступен, а страница входа отдавалась без шифрования. Привязка
домена пересоздана, сертификат выпущен на gym-bro.ru и www.gym-bro.ru,
Enforce HTTPS включён.
Переписка переехала в базу: возвращаешься во вкладку — она на месте, и на телефоне та же. Модели отправляется хвост в 20 сообщений.
У помощника появилась память: он сам отмечает долгоиграющее — травму, инвентарь, цель — и говорит об этом в переписке. Запомненное видно сворачиваемым списком, любой пункт удаляется. На подбор тренировок память не влияет, только на разговор.
Пока модель отвечает, видно три точки и подпись, чем именно она занята: «собираю тренировку», «смотрю историю подходов». Раньше пауза читалась как зависание.
У тренировок, собранных помощником, убран разбор «Что поправить». Он считает неделю целиком, а одна тренировка её закрыть не может, поэтому совет «добавьте ещё» выходил всегда — и читался как «исправь то, что я тебе только что дал».
Признак «собрано помощником» стал явным: раньше он выводился из наличия пояснения к программе, и тренировки от помощника им не помечались.
Вкладка GymBro получила поле для свободного вопроса. Ответ считает
deepseek-v4-flash через серверную функцию Supabase — ключ лежит там и в
браузер не попадает.
Модель видит профиль и объём за неделю и может попросить приложение собрать тренировку, открыть группу на схеме или показать историю подходов в упражнении. Выполняет всё приложение: программы собирает свой алгоритм, а не модель.
Названия групп и упражнений, которых нет в каталоге, отбрасываются — модель не может подставить вместо них похожие. Испорченные аргументы вызова не роняют разговор, а возвращаются модели сообщением об ошибке.
Готовые вопросы остались и считаются на месте: они работают, даже когда модель недоступна.
Выбор группы на схеме тела переехал в общее состояние — иначе «покажи на схеме» переключало вкладку, но ничего не показывало.
Исправлен хук запуска тестов: он дописывал «.ts» к путям внутри зависимостей, из-за чего падал любой тест, который тянет клиент Supabase.
Домен gym-bro.ru уже был прописан в настройках GitHub Pages, из-за чего
старый адрес derfi228.github.io/gym-bro стал перекидывать на него. Сборка
при этом продолжала считать, что живёт в подпапке.
Добавлен web/public/CNAME (иначе привязка слетает при каждой публикации),
базовый путь убран. Проверено на собранных файлах: ссылок на /gym-bro/
не осталось, пути идут от корня.
Ключи проекта прописаны в репозитории (vars и secrets), поэтому вход
работает и на живом сайте, а не только локально.
В базу пишутся веса по упражнениям, свои тренировки, проведённые тренировки и каждый выполненный подход. Веса и программы уходят с задержкой 0,8 с: поле веса правится на каждое нажатие клавиши, и без задержки каждое нажатие было бы запросом.
Недельный объём на схеме тела теперь считается из записанных подходов, а не берётся из зашитых цифр. Демонстрационные значения остались для тех, кто не вошёл.
Добавлено задание keepalive.yml: раз в трое суток дёргает базу. Бесплатный
проект Supabase останавливается после недели простоя, и сайт переставал бы
пускать людей до ручного нажатия в панели.
Исправлено: подход мог попасть в базу дважды. Записи стояли внутри функции обновления состояния, а React вправе вызвать её повторно.
Идентификаторы своих тренировок стали uuid — прежние строки вида
prog-<время>-<число> не подходят типу ключа в базе.
Убран resetWeek: он возвращал демонстрационные цифры, что после подключения
базы стало просто неверным. Им никто не пользовался.
Над схемой тела вместо зашитых «182 см / 78 кг / 24 года» — профиль из базы. Полоска раскрывается в форму: рост, вес, год рождения, пол, опыт. Сохранение идёт одним запросом и правит только изменённые поля.
Помощник перестал называть выдуманные цифры: пока профиль пустой, он про него молчит вместо того, чтобы считать «от 182 см».
Перевод между базой и моделью вынесен в web/src/lib/profile.ts — там же
рамки значений, повторяющие проверки в самой базе, и склонение возраста
(«21 год», «24 года», «25 лет»). На всё это есть тесты.
Проверено по живой базе: имена всех колонок, которые пишет код, сверены запросом к схеме — выдуманная колонка отвергается, наши проходят.
Из shared/types.ts убрана функция: псевдоним @shared настроен для
TypeScript, но не для сборщика, и значение оттуда роняет сборку. Файл остаётся
только с типами, правило записано в CLAUDE.md.
Подключён Supabase: вход по почте и паролю, шесть таблиц с построчным доступом, профиль заводится триггером вместе с аккаунтом.
Каталог упражнений открыт без регистрации. Схема тела, свои тренировки, помощник и челленджи просят войти. При первом заходе — выбор: создать аккаунт, войти или посмотреть без него; выбор запоминается. Вход и выход — в правой части шапки.
Пока ключи проекта не переданы в сборку, вход выключен целиком и приложение работает как раньше. Проверено сборкой без ключей: замков нет, ключ в файлы сборки не попадает.
В модели профиля вместо возраста хранится год рождения — записанный однажды возраст через год становится неверным.
Не сделано: данные приложения (веса, программы, история тренировок) пока живут в памяти вкладки и в базу не пишутся, помощник к модели не подключён.
Поиск в каталоге ищет и по группам мышц. Раньше окно поиска смотрело только в название, поэтому «спина» не находила тяги и подтягивания. Теперь в строку поиска попадают названия целевой и вспомогательных групп.
Минимум тренировки — 30 минут. Поле, ползунок и ориентир у готовой программы начинаются с 30; у помощника кнопка «короткой» тоже стала на 30. Расчётная длительность тренировки, собранной вручную из каталога, по-прежнему считается по её содержимому — это измерение, а не выбор.
Сборка программы переписана и вынесена в web/src/lib/build.ts. Раньше
слоты набирались по кругу от самой отстающей группы и в тренировку попадало
то, что окажется первым в каталоге. Что изменилось:
- на каждом шаге берётся упражнение, закрывающее больше всего недобора, а базовые движения идут с множителем — тренировка держится на приседе, тяге и жиме, а не на наборе изоляции;
- прямая работа весит вдвое против попутной, иначе самая пустая группа (икры) никогда не получала своего упражнения;
- одно движение не берётся дважды: присед со штангой заведён в каталоге под квадрицепс и под ягодицы, и обе записи попадали в одну тренировку;
- не больше двух упражнений на группу;
- порядок выполнения — базовые вперёд, пресс и икры в конец;
- остаток времени идёт в подходы уже выбранных движений, а не в девятое упражнение: раньше тренировка на 90 минут занимала 42.
Логика вынесена из store.tsx, потому что там она тянула за собой React и не
запускалась отдельно. Теперь на неё есть шесть проверок в build.test.ts.
Замена упражнения прямо на тренировке. В карточке текущего упражнения появилась кнопка «Заменить»: список составлен из упражнений на ту же целевую группу. Подходы, повторы и отдых остаются от слота, сделанные подходы не сгорают. Список закрывается сам при переходе к следующему упражнению.
Из каталога убраны подписи «№N»: место внутри одного исследования нельзя сравнивать с местом из другого, а в общем списке они стояли рядом.
Для спины и пресса найдены и проставлены измеренные значения: таблица 4 диссертации Edelburg (средний %MVC широчайшей, нормировано к подтягиваниям) и таблица ACE/SDSU 2001 (% активности прямой мышцы, нормировано к велосипеду). Упражнений с проверенным числом стало 26 из 62 вместо 15.
Скручивания на блоке больше не ссылаются на исследование ACE — их там не тестировали.
В карточке появились две шкалы: активация синяя, сложность янтарная. Шкала активации подписана целевой мышцей — под фильтром по вспомогательной группе процент читался как её активация, хотя относится к целевой.
Текст переведён на Manrope: системный стек спорил с серифом в заголовках. Фон стал многослойным — источники света, тёплый отсвет снизу, зерно, виньетка и медленно дрейфующее свечение. Карточки получили блик по верхней кромке и тень, карточки-кнопки приподнимаются при наведении.
Тёплый цвет повышен до второго: холодный несёт данные, тёплый — действие,
поэтому кнопки старта тренировки янтарные. Заголовки разделов набраны
серифом с линией до края и делят длинную страницу на главы. Содержимое
вкладки появляется лесенкой через :nth-child, без правки разметки.
Тренировка запускается из программы или дня методики и идёт в первой вкладке: отсчёт со звуком, текущее упражнение с рекомендуемым весом, отметка подхода с оценкой, таймер отдыха, пауза и время тренировки. Оценка подхода двигает рабочий вес, отмеченный подход попадает в недельный объём.
Рекомендуемый вес берётся из рабочего, а при его отсутствии считается из пикового по формуле Эпли. Если не задано ничего — рекомендации нет.
Логика тренировки лежит в web/src/lib/session.ts без React и проверяется
через node --test src/lib/session.test.ts — без тестового фреймворка.
Незатронутые мышцы на схеме стали серыми вместо бледно-синих: раньше они читались как слегка задействованные.
В каталоге появились поиск по названию, пресет «Свой вес» и сортировка по алфавиту; все условия складываются. Карточка упражнения показывает, в каких программах оно стоит, и позволяет добавить его в свою тренировку.
Программы разделены на пользовательские, известные методики и разовые тренировки; пользовательские идут первыми. Всё встроенное подписано автором GymBro и переведено в режим частичной правки: упражнение можно заменить на другое для той же группы, включая упражнения внутри дней методик. Свои тренировки редактируются целиком, дублируются и удаляются.
Добавлен ручной сбор тренировки: каталог открывается в режиме выбора с полем
названия, крестиком для сброса и галочкой для подтверждения. Место под оценку
модели заведено (Program.aiScore), но пока показывает прочерк — модели нет,
числа не выдумываются.
Шесть групп состояли из нескольких брюшек с зазорами и читались как нарезка. Дольки слиты морфологическим замыканием, контур переразмечен и переведён в кривые Безье со сглаживанием Тобина — оно не съедает объём мышцы. Затронуты предплечья и квадрицепс спереди, предплечья, трицепс, спина, бицепс бедра и икры сзади. Икры и трицепс спереди не менялись: там по одному пути на сторону.
Убрана разделительная обводка цветом фона между путями — она резала мышцу на
плитки. Контуры переведены на non-scaling-stroke: толщина задаётся в
пикселях экрана и не съедается уменьшением фигуры. Сама фигура увеличена до
330 px. Выделяется только выбранная группа.
Под группы добавлен BODY_OUTLINE — силуэт целиком, залитый фоном. Раньше
промежутки между группами показывали фон карточки, и вид сзади, где почти все
группы отстают, читался как набор фрагментов.
Нарисованные вручную контуры заменены на геометрию из
react-native-body-highlighter
(MIT): 13 групп покрыты полностью, пресс разбит по сегментам, левая и правая
стороны заданы отдельно — зеркалирование убрано. Пути вынесены в bodyPaths.ts,
логика заливки, статусов и кликов не менялась.
Объём вынесен в landmarks.ts: MEV / MAV / MRV на 13 групп, целевые цифры
больше не захардкожены в загрузке. Схема тела получила заливку по статусу —
не затронута, мало, в диапазоне, много, перебор — и плавную анимацию высоты.
Вкладка «Программа» стала конструктором: подходы меняются шагом прямо в списке,
упражнения переставляются и убираются, добавление идёт от группы. Тело рядом
пересчитывается на каждое изменение, переключается между «только тренировка» и
«неделя целиком». Блок «Что поправить» показывает пропущенные и перегруженные
группы и применяет правку одной кнопкой. Ползунок времени больше не режет
программу — только показывает расхождение с ориентиром.
Методики раскрываются в список тренировочных дней с упражнениями; упражнение открывается в карточке. Шаг полей веса — 1 кг. Мышцы на карте получили отдельные контуры, выбранная группа выделяется толщиной и цветом.
Подключена схема с проекциями спереди и сзади, добавлены спина, ягодицы и бицепс бедра. Каталог расширен до 62 упражнений с описанием техники и картой вовлечённости. Упражнения открываются в карточке с подсветкой мышц и полями пикового и рабочего веса. В программы добавлен справочник методик. Из вкладки «Упражнения» убраны подписи источников.
Добавлена группа «трицепс», фигура перерисована. Каталог переведён на данные
ЭМГ-исследований с полем evidence. Появился общий стор: отметка подхода
заполняет карту мышц, GymBro читает объём и создаёт программы. Вкладка
«Программа» разбита на три уровня, длительность задаётся произвольно.
Заставка при входе, нижняя панель на пять вкладок вместо прокрутки. Вкладки стали интерактивными.
Первая версия сайта, модель данных в shared/types.ts, статический экспорт и
workflow для GitHub Pages.
Созданы web/, mobile/, shared/.
- Подключить Supabase: авторизация, база, storage
- Экраны мобильного приложения — сейчас шаблон Expo
- Языковая модель для GymBro вместо локальной логики подбора
- Расчёт целевого объёма по росту, весу и возрасту — сейчас общий для всех
- Исследования по трапециям, предплечьям и косым — часть значений
estimate - Построение карты по фото пользователя
- Сохранение данных: состояние живёт в памяти вкладки
- Landmarks с
basis: "model"— найти исследования вместо ориентиров