# Backlog Calendar Run **Срез репозитория: 12 июля 2026.** Это единый продуктовый и технический backlog, составленный по текущему коду, а не список абстрактных идей. ## Текущая база | Контур | Версия | Уже работает | |---|---:|---| | Backend | `1.6.0` | Express + PostgreSQL, миграции и seed, строгий CRUD личных стартов, фильтры истории, cookie-сессии, CSRF, подтверждение email, Turnstile, SMTP, смена пароля и управление сессиями, SSRF-защита и восстановление обложек, `/health`, `/ready`, `/meta`, CI с PostgreSQL. | | Frontend | `0.9.0` | Защищённые маршруты и auth-экраны, экран аккаунта, поиск/фильтры/экспорт истории, нативные поля даты/старта, удобный ввод результатов, дашборд с последними стартами и графиком AG Charts, версия FE/BE в футере. | ## Правила версионирования и выпуска 1. BE и FE выпускаются независимо: задачам без серверного контракта не нужен искусственный bump BE. 2. Патч (`x.y.Z`) — исправление без изменения публичного контракта; минор (`x.Y.0`) — новая обратносуместимая возможность; мажор — только при несовместимом API или модели доступа. 3. Любой релиз с изменением API включает: обновлённый `docs/backend-api-for-frontend.md`, миграцию (если нужна), тесты контракта и проверку футера с фактическими версиями. 4. До миграций в production выполнить чек-лист из [auth-rollout.md](auth-rollout.md): backup, число стартов до/после миграции и проверка владельца legacy-данных. ## План релизов | Очередь | BE | FE | Цель | Условие выпуска | |---|---:|---:|---|---| | P0 | `1.4.2` | `0.7.1` | Исправить риски текущего релиза и привести контракт в соответствие коду. | Нет невалидных записей, SSRF-защита, критические сценарии проверяются на PostgreSQL. | | P1 | `1.6.0` | `0.9.0` | Дать пользователю контроль над аккаунтом и данными, улучшить работу с историей стартов. | Управление сессиями и экспорт доступны, фильтры не ломают существующий список. | | P2 | `1.7.0` | `1.0.0` | Подготовка к старту: чек-листы, напоминания и календарная интеграция. | Напоминание отправляется не более одного раза, подписка/токен отзываемы. | | P3 | `1.8.0` | `1.1.0` | Сделать персональный календарь эксплуатационно готовым. | Восстановление из backup проверено, есть E2E критического пути и аудит доступности. | `2.0.0` не планируется заранее: его поводом может быть только подтверждённая потребность в совместном доступе, командах или публичных стартах — это изменит модель владельцев и доступа. ## P0 — BE 1.4.2 / FE 0.7.1 ### Backend 1.4.2 | ID | Приоритет | Задача | Результат / критерий приёмки | |---|---|---|---| | BE-142-04 | P0 | Выполнить backend-часть Ponytail-аудита | Pipeline поднимает чистый PostgreSQL, применяет все миграции и выполняет минимум CRUD + auth, включая legacy-сценарий миграции `004`. Самодельный SQL-эмулятор `CALENDAR_RUN_MOCK_DB` удалён; тесты работают с этой БД. `cross-env` удалён, если Windows не входит в список поддерживаемых сред разработки. | | BE-142-01 | P0 | Актуализировать контракт API | Документированы `id` (UUID сервера) и `slug` (ключ пользователя), обязательная авторизация для `/races`, CSRF для мутаций, реальный ответ `/health`, все коды ошибок и формат `Race`. Старое описание не обещает несуществующий неавторизованный CRUD. | | BE-142-02 | P0 | Ввести строгую валидацию создания и изменения старта | Одна схема на `POST`/`PATCH`: корректные дата и дистанция, enum статуса, допустимые форматы `startTime` и `finishTime`, URL, пределы длины текстов; неизвестные поля не игнорируются. Ошибка возвращает `400 validation_error` с полем/причиной. | | BE-142-03 | P0 | Защитить получение обложки от SSRF | Автозагрузка принимает только безопасные HTTP(S)-адреса, запрещает loopback/private/link-local адреса и опасные редиректы, ограничивает число редиректов, размер и время ответа. При невозможности получить обложку старт всё равно создаётся без неё. Есть тесты на блокировку. | | BE-142-05 | P1 | Подтвердить достаточность индексов по фактическим запросам | Для списка владельца с фильтром года/месяца снять `EXPLAIN ANALYZE`; индекс `(owner_user_id, race_date)` добавлять только если он нужен по плану запроса или размеру данных. | ### Frontend 0.7.1 | ID | Приоритет | Задача | Результат / критерий приёмки | |---|---|---|---| | FE-071-00 | P0 | Выполнить frontend-часть Ponytail-аудита | Удалены артефакты TypeScript/Vite из Git и добавлены в `.gitignore`, неиспользуемый `getHealth` и тип `HealthResponse`. Кастомные поля даты и времени старта заменены нативными `input[type=date]` и `input[type=time]`; каталог названий стартов с внешними картинками удалён, остаются `coverImageUrl` и локальные fallback-изображения. Создание и обновление старта используют один payload полей. | | FE-071-01 | P0 | Исправить семантику личного рекорда | Карточка «Последний личный рекорд» показывается только для результата, который действительно улучшил прошлое лучшее время на сопоставимой дистанции; иначе карточка называется «Последний результат» либо скрывается. | | FE-071-02 | P0 | Валидировать время до отправки | Форма не принимает `1:99`, отрицательные и произвольные значения; пользователю показана ошибка у поля. Правило идентично серверному, но сервер остаётся источником истины. | | FE-071-03 | P0 | Довести ошибки auth-форм до состояния UI | На регистрации, повторной отправке письма, запросе и сбросе пароля есть loading-состояние, обработка ошибки и защита от повторной отправки. Никаких необработанных Promise в браузере. | | FE-071-04 | P1 | Корректно обработать истёкшую сессию и CSRF | После `401` пользователь возвращается на вход с сохранением целевого маршрута; после `csrf_error` интерфейс предлагает однократно обновить сессию и повторить действие, не теряя заполненную форму. | | FE-071-05 | P0 | Завершать подтверждение email понятным экраном успеха | После успешной ссылки `/verify-email?token=…` заголовок явно сообщает, что email подтверждён; ввод email, инструкция и кнопка повторной отправки не показываются. Форма повторной отправки остаётся только для недействительной/отсутствующей ссылки. | | FE-071-06 | P0 | Упростить ввод результата забега | Время вводится отдельными цифровыми полями «часы», «минуты», «секунды» без ручного ввода `:`. Часы — целое число ≥ 0, минуты и секунды — от 0 до 59; пустые минуты/секунды автоматически нормализуются в `0`. Итоговое время не показывает незаполненные старшие части: без часов — `MM:SS`, без часов и минут — `SS`. Место вводится числами «место» и «всего участников» (второе необязательно), проверяется, что место ≥ 1 и не больше общего числа; в текущий `finishPlace` без изменения API сохраняется `место/всего` либо только место. Старые значения `finishPlace` не теряются до редактирования результата. | ## P1 — BE 1.6.0 / FE 0.9.0 ### Backend 1.6.0 | ID | Приоритет | Задача | Результат / критерий приёмки | |---|---|---|---| | BE-150-01 | Готово | Управление паролем и сессиями | Выполнено в `1.5.0`: авторизованный пользователь меняет пароль после проверки текущего; все другие сессии отзываются. Есть список активных сессий с отзывом выбранной и «выйти везде». Токены и пароли в ответах/логах не появляются. | | BE-150-02 | P1 | Удаление аккаунта и данных | Явно подтверждённый запрос удаляет аккаунт, его сессии и личные старты транзакционно; повторный запрос идемпотентен. Перед реализацией согласовать необходимость периода восстановления. | | BE-150-03 | Готово | Обратносуместимая фильтрация стартов | Добавлены документированные фильтры `status`, диапазон дат и диапазон дистанции без изменения ответа-массива `GET /races`, с валидацией параметров. | | BE-150-04 | Готово | Восстановить обложки существующих стартов | Добавлен идемпотентный backfill и обновление обложки при изменении официальной страницы. SSRF-защищённая логика переиспользуется; ручные URL не перезаписываются, результат содержит агрегированную статистику. | ### Frontend 0.9.0 | ID | Приоритет | Задача | Результат / критерий приёмки | |---|---|---|---| | FE-080-01 | Готово | Экран «Аккаунт» | Выполнено в `0.8.0`: пользователь видит подтверждённый email, меняет пароль и видит/отзывает другие сессии. Удаление аккаунта остаётся отдельной задачей BE-150-02. | | FE-080-02 | Готово | Поиск, фильтры и сортировка истории | Фильтры по статусу, периоду и дистанции, поиск по названию и сортировка синхронизированы с URL. Годы в селекторе строятся из данных. | | FE-080-03 | Готово | Выгрузка личных данных | Из текущей отфильтрованной выборки пользователь скачивает CSV и одноразовый `.ics` в браузере; сервер не нужен. | | FE-080-04 | Готово | Сделать на дашборде список последних стартов | Выполнено в `0.8.0`: «Последние старты» показывают до 5 завершённых стартов с клиентской пагинацией, фильтрами по дистанции и году и доступным переходом в старт. | | FE-080-05 | Готово | Связать рекорды по дистанциям со стартами | Выполнено в `0.8.0`: карточка рекорда с найденным стартом ведёт на `/races/:id` и использует существующую анимацию связанных карточек. | | FE-080-06 | Готово | Сделать график прогресса информативным и интерактивным | Выполнено в `0.8.0`: график имеет подписанные оси, компактные подписи, tooltip по наведению/фокусу и доступный переход в старт. | | FE-080-07 | Готово | Вернуть управление официальной страницей старта | Поле «Официальная страница старта» доступно для любого старта, включая прошедший; для прошедших скрываются только неактуальные поля расписания. | | FE-080-08 | Готово | Перевести график прогресса на AG Charts | Самописный SVG заменён на AG Charts; сохранены данные, оси, tooltip, клавиатурная доступность, переход по точке, адаптивность и пустое состояние. | ## P2 — BE 1.7.0 / FE 1.0.0 ### Backend 1.7.0 | ID | Приоритет | Задача | Результат / критерий приёмки | |---|---|---|---| | BE-160-01 | P1 | Чек-листы подготовки к старту | Новая сущность задач старта: текст, состояние, срок и порядок. Задачи принадлежат тому же пользователю, что и старт; каскадно удаляются вместе со стартом. | | BE-160-02 | P1 | Надёжные email-напоминания | Настройки пользователя и очередь напоминаний хранятся в БД; отправка идемпотентна, ошибки ретраятся ограниченно и логируются без PII. Расписание запуска — внешний cron/worker, а не `setInterval` в каждом web-инстансе. | | BE-160-03 | P2 | Подписка на персональный iCalendar | Выдать отдельный отзывной секретный URL только для read-only календаря пользователя. Нужны лимитирование, отсутствие email/заметок в ICS и отзыв/ротация токена. | ### Frontend 1.0.0 | ID | Приоритет | Задача | Результат / критерий приёмки | |---|---|---|---| | FE-090-01 | P1 | Чек-лист в карточке старта | Пользователь добавляет, завершает и переставляет задачи подготовки; на дашборде видит ближайшие незавершённые пункты. Пустое состояние объясняет, что делать. | | FE-090-02 | P1 | Настройки напоминаний | Опт-ин по email и выбор сроков до старта; понятно отображаются часовой пояс и факт отключения рассылки. | | FE-090-03 | P2 | Управление iCalendar-подпиской | Экран показывает URL только по явному действию, даёт скопировать, отозвать и выпустить новый. Одноразовый экспорт из `0.8.0` остаётся доступным без подписки. | | FE-090-04 | P2 | Исправить полную TypeScript-проверку frontend | `tsc -b` проходит без ошибки `Cannot find name 'Worker'` в типах Vite; конфигурации browser/node остаются корректно разделены, проверка исходников не ослабляется. | ## P3 — BE 1.8.0 / FE 1.1.0 ### Backend 1.8.0 | ID | Приоритет | Задача | Результат / критерий приёмки | |---|---|---|---| | BE-170-01 | P0 | Эксплуатационный runbook | Описаны deploy, миграция, rollback, backup, restore, ротация секретов, SMTP/Turnstile и реакция на недоступную БД. Restore протестирован на копии production-данных. | | BE-170-02 | P0 | Наблюдаемость без утечки данных | Структурированные логи с request ID, готовность БД, счётчики ошибок и alert на недоступность. Email, пароли, токены, cookie и текст заметок не логируются. | | BE-170-03 | P1 | Машиночитаемый API-контракт | OpenAPI-спецификация синхронизирована с маршрутам и используется в CI для проверки ключевых ответов. Это предотвращает повторное расхождение документации и реализации. | ### Frontend 1.1.0 | ID | Приоритет | Задача | Результат / критерий приёмки | |---|---|---|---| | FE-100-01 | P0 | Критический E2E-путь | Автоматически проверяются: регистрация/подтверждение (с тестовым адаптером), вход, создание старта, внесение результата, экспорт и выход. Тест запускается в CI против реального API и PostgreSQL. | | FE-100-02 | P0 | Accessibility и мобильный аудит | Полный путь проходится клавиатурой; формы сообщают ошибки screen reader'у, focus не теряется после действий, контраст и touch-targets соответствуют WCAG 2.2 AA. Проверены малый экран и медленное соединение. | | FE-100-03 | P1 | Полировка надёжности UI | Единые skeleton/empty/error состояния, retry для безопасных загрузок, защита от двойной отправки мутаций, проверка загрузки внешних обложек и сохранение версии FE/BE в футере. | ## Зависимости и решения до начала P2 | Решение | Нужен ответ | Почему блокирует | |---|---|---| | Канал напоминаний | Только email или ещё push/мессенджер? | Определяет модель согласия, worker и стоимость эксплуатации. | | Планировщик | Какой внешний cron/worker доступен в production? | Нельзя надёжно рассылать напоминания таймером в web-процессе. | | Удаление аккаунта | Немедленное удаление или 30-дневное восстановление? | Меняет схему данных и обязанность хранить архив. | | Политика данных | Для каких стран и пользователей запускается сервис? | До публичного запуска нужны политика конфиденциальности, срок хранения и процесс обращения пользователя. | ## Явно не берём до подтверждения спроса - общий доступ к календарям, клубы, тренеры и публичные профили; - поиск и парсинг стартов из сайтов организаторов, кроме уже существующей необязательной обложки; - нативные iOS/Android-приложения, платежи и социальная лента; - серверную пагинацию: личный календарь пока мал, а изменение формы ответа `GET /races` без измеренной необходимости только усложнит FE и API. ## Рекомендуемый первый спринт 1. `BE-142-04` и `FE-071-00`: выполнить выводы Ponytail-аудита — удалить самодельную инфраструктуру и использовать возможности платформы. 2. `BE-142-01` и `BE-142-02`: зафиксировать настоящий контракт и закрыть trust boundary. 3. `BE-142-03`: убрать SSRF-риск до любого расширения автозагрузки обложек. 4. `FE-071-01`–`FE-071-06`: устранить неверную аналитику и ошибки критических форм, включая понятное завершение подтверждения email и ввод результата. ## Выполненный спринт **Цель:** пользователь быстро находит старты, восстанавливает данные истории, выгружает результат и видит прогресс в поддерживаемой chart-визуализации. 1. `BE-150-04` + `FE-080-07`: восстановление обложек существующих стартов и управление официальной страницей, включая прошедшие старты. Ручные обложки не перезаписываются; используется существующая SSRF-защищённая логика. 2. `BE-150-03` + `FE-080-02`: фильтры по статусу, периоду и дистанции, поиск по названию, сортировка и синхронизация с URL. Обновить API-документацию и контрактные тесты. 3. `FE-080-03`: экспорт текущей отфильтрованной выборки в CSV и одноразовый `.ics` без нового backend и зависимостей. 4. `FE-080-08`: заменить самописный SVG-график прогресса на AG Charts. Сохранить текущие данные и метрики, подписи осей, tooltip, клавиатурную доступность, переход по точке в старт, адаптивность и пустое состояние. Не менять API и не добавлять новую обёртку поверх `PaceTrendChart`. `BE-150-02` в спринт не брать до решения: удаление аккаунта сразу или с 30-дневным восстановлением.