Files
laude/CHANGELOG.md
AnRil 0cace2975d chore(remote): миграция Gitea-URL на сабдомен git.
Gitea переехал с path-prefix (xn--90adajar8af4h.xn--p1ai/git/) на
выделенный сабдомен (git.xn--90adajar8af4h.xn--p1ai). Старый URL теперь
отдаёт чужое приложение и для git мёртв.

- package.json: publish.url (канал авто-апдейта) -> новый хост
- scripts/release.ps1, upload-release-assets.ps1: $giteaHost (API + release URL)
- README, CHANGELOG, RELEASING.md, CLAUDE.md: ссылки на репозиторий/релизы

Прим.: уже установленные копии (<=0.5.8) запекли старый URL в бинарник —
их авто-апдейт нужно мигрировать отдельно (bridge-теги), правкой конфига
это ретроактивно не лечится.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 22:03:16 +07:00

30 KiB
Raw Permalink Blame History

Changelog

Все заметные изменения проекта документируются здесь. Формат основан на Keep a Changelog, проект следует Semantic Versioning.

Unreleased

0.5.8 — 2026-05-22

Автономный QA-проход: проверил все элементы, нашёл и починил несколько коварных багов, добавил тесты на новые модули.

Fixed

  • Heatmap / streak / достижения теперь обновляются после markDone. Был регресс из Sprint C (#9 — отделение history от broadcastState): markDone мутирует Exercise in-place → state.exercises ref не меняется → Dashboard useEffect с дептой [exercises] не fire'ил → history не перетягивалась. Heatmap стоял пока пользователь не добавит/удалит упражнение. Сейчас новый event evtHistoryChanged шлётся из main после markDone/snooze/skip/markChallengeDone/ clearHistory/import, Dashboard на него подписан.
  • Rapid double-click больше не пишет в историю дважды. В Match Summary при быстром тыке ✓ дважды один и тот же challenge мог записаться 2 раза → лишние +N reps в стрик. То же для кнопки «Готово» в ExerciseCard. ref-based дедуп.
  • Native save/open dialogs локализованы. Раньше title «Сохранить резервную копию» показывался даже в EN-локали.
  • Default exerciseName в challenge editor — пустой (было «Приседания» — выглядело как недопереведённый русский в EN UI).

Added

  • 18 новых тестов: achievements.test.ts (10), расширения history.test.ts (8) — match-challenges через snapshot, deleted exercise survival, race-edge cases.

0.5.7 — 2026-05-22

Сквозное ревью UX: пройдено 12 сценариев глазами пользователя, найдено 13 проблем (3 настоящих бага P0 + 4 UX-просадки P1 + 6 мелочей P2), все починены.

Fixed (P0 — функциональные баги)

  • Match-челленджи теперь пишутся в историю. Раньше клик ✓ в окне Match Summary обновлял только локальный Set<challengeId> — челленджи не доходили до store, и стрик / today_done / достижения игнорировали игровые тренировки (самую главную фишку приложения). Сейчас IPC markChallengeDone пишет entry с source='match', exerciseId='challenge:<id>', actualReps, reps (snapshot).
  • Tray-пауза синхронизирована с Dashboard. Раньше «Пауза напоминаний» из tray использовала scheduler-local paused boolean, который не отражался в settings.globalEnabled — Dashboard показывал «running» с тикающим таймером, хотя fires не приходили. Сейчас единый source of truth — settings.globalEnabled. setPaused/isPaused удалены, IPC pauseAll/resumeAll переписаны на updateSettings.
  • WhatsNew покажется обновляющимся пользователям. В v0.5.6 логика была: lastSeenVersion === undefined → silent save (не показывать). Это означало что никто из текущих пользователей при апгрейде с v0.5.5 не увидит описание новых фич. Сейчас: если есть Exercise с lastDoneAt — пользователь существующий, показываем заметки текущей версии. Иначе (новичок) — silent.

Fixed (P1 — UX просадки)

  • Удаление упражнения теперь спрашивает подтверждение. Раньше один клик в menu «Удалить» сразу удалял. Сейчас iOS-style ConfirmModal с destructive-кнопкой.
  • Daily goal закрыт — больше не «25 часов 13 минут». Когда дневная цель достигнута, ExerciseCard показывает «Цель закрыта · 100/100» с success-зелёным цветом, а не запутанный обратный отсчёт до завтра.
  • Авто-пауза на ВКС видна в Dashboard. Раньше fires пропускались молча — пользователь не понимал почему через 12 мин ничего не пришло. Сейчас info-баннер «Не дёргаем — ты на встрече» с указанием закрыть Zoom/Teams/etc.
  • Native window.confirm() → iOS-style ConfirmModal в restore-операции. Раньше всплывал серый системный диалог.

Fixed (P2 — полировка)

  • Achievement unlock celebration. Когда впервые открылся новый badge — pulse + accent-glow анимация 2.8 сек. Список celebrated хранится в localStorage, при следующем заходе анимации нет.
  • Match Summary close confirm. При закрытии окна с незакрытыми челленджами спрашиваем подтверждение с указанием остатка.
  • TTS задержка 800мс. Дикторский голос даёт пользователю шанс decrement'нуть stepper — иначе TTS произносил планируемые reps в тот момент когда юзер уже изменил цифру.
  • Tracking badge точность. Раньше зелёный показывался даже при queued launch option. Сейчас 3 состояния: live (success, всё работает), setup (warning «закрой Steam и снова открой» если launch option ещё не применён), off (muted, не подключено).
  • HistoryEntry хранит snapshot reps+name+source. Heatmap и achievements больше не теряют данные после удаления упражнения.
  • Adaptive-badge на ExerciseCard. Маленькая Brain-иконка показывает, что упражнение в адаптивном режиме — пользователь понимает почему Next не строго равен intervalMinutes.

Added

  • src/renderer/src/components/ui/ConfirmModal.tsx — переиспользуемый iOS-style confirm с focus-trap'ом через Modal.
  • IPC markChallengeDone(challengeId, reps) — handler в main, метод в preload (раньше канал был в IPC enum, handler не зарегистрирован).
  • IPC getMeetingActive + event evtMeetingChanged — meeting-detect broadcast'ит при переходе on/off.
  • Helper repsDoneTodayForExercise(history, exercise) — per-exercise daily count для UI индикатора goal.

0.5.6 — 2026-05-22

Большой релиз с 7 новыми фичами + экран «Что нового», который покажется автоматически после установки этой версии.

Added

  • Категории напоминаний (#7) — кроме упражнений теперь hydration / eye-rest (20-20-20) / posture. Каждая со своим CTA в окне напоминания. В SAMPLE добавлены примеры.
  • TTS-голосовые подсказки (#8) — диктор произносит название упражнения и количество. Opt-in в Settings → «Голосовая подсказка». Web Speech API: подбор голоса под язык (ru-RU / en-US).
  • Достижения (#10) — milestones по total reps (100/500/1000/5k/10k), streaks (3/7/14/30/100 дней), first_day, today_quad. На Dashboard карточка с unlocked + 2 ближайших по прогрессу. Прогресс-бар до следующего достижения.
  • Дневная цель (#12) — soft cap reps/день в exercise editor. Когда total reps за сегодня (с actualReps) ≥ dailyGoal → scheduler переносит fire на завтра. История = source of truth.
  • Авто-пауза на ВКС (#5) — сканирует процессы tasklist'ом раз в 30с: Zoom/Teams (старый+new)/Discord/Webex/Slack/Skype/Meet/Whereby/ GoToMeeting. Если запущен — fires не выполняются.
  • Адаптивный шедулер (#2) — opt-in флаг в exercise editor. Heuristic-модель строит hour-of-day success rate по 30 дням истории (≥10 событий обязательно). При попадании fire в «плохой» час (success ≤ 30%) сдвигает на ближайший «хороший» (≥50%), в пределах 4 часов.
  • Export / Import (#9) — Settings → Data. Native save/open dialogs Electron. JSON-snapshot всего persisted-state (включая историю). Import = replace, не merge — UI просит подтверждение.
  • Экран «Что нового» — модалка с заметками релизов. Показывается автоматически когда lastSeenVersion ≠ текущей версии (после обновления). Доступна также из Settings → «О приложении». Реестр заметок в src/shared/release-notes.ts, версионируется per-app.

Changed

  • Settings.lastSeenVersion (optional) — отслеживает что пользователь видел.
  • IPC.getAppVersion → app.getVersion() для renderer.
  • DEFAULT_SETTINGS: добавлены voicePromptsEnabled: false, meetingAutoPause: true.
  • Exercise type расширен: category?, dailyGoal?, adaptive?. Все обратно совместимые (optional).

Performance

  • Scheduler запрашивает history только если есть упражнения с dailyGoal или adaptive — иначе экономит IPC.

0.5.5 — 2026-05-22

Большой sweep по ревизии: 4 спринта правок (≈14 пунктов), все 135 тестов зелёные. Главное — UI больше не залипает при retry'ях I/O, GSI порт не зависает в TIME_WAIT после выхода, sandbox включён, шрифты self-hosted.

Security

  • sandbox: true на обоих BrowserWindow. Preload использует только contextBridge + ipcRenderer (sandbox-safe), никаких Node-built-ins. OS-уровневый sandbox изолирует renderer на уровне процессов — даже RCE в зависимости рендерера не получит Node-доступа через preload.
  • CSP ужесточён. Убраны https://fonts.googleapis.com и https://fonts.gstatic.com origins (шрифты теперь self-hosted), добавлены connect-src 'self', base-uri 'self', frame-ancestors 'none'.

Added

  • Self-hosted шрифты. Plus Jakarta Sans, Bricolage Grotesque, JetBrains Mono подключены через @fontsource/* пакеты — в bundle лежат локально, без интернета шрифты работают, CSP без внешних origins. +22 .woff/.woff2 (~500KB) в installer.
  • src/main/logger.ts — структурный logger с уровнями (debug/info/warn/error) и ротацией. Пишет в %APPDATA%/Exercise Reminder/logs/latest.log (≤1MB) и дублирует в console. При 1MB ротируется в prev.log. LAUDE_DEBUG=1 включает debug-уровень. Подключён в hot paths: store, updater, GSI server, registry, dota2 provider — особенно полезно для диагностики «челленджи не срабатывают» (видно token verify, POST_GAME detection, фильтрацию challenges).
  • <html lang> синхронизируется с settings.language через ThemeProvider — screen readers корректно произносят язык.
  • dev:simulateMatchEnd channel вынесен в IPC enum (IPC.devSimulateMatchEnd).
  • test:coverage npm script.

Changed

  • broadcastState больше не шлёт history через IPC. Раньше каждый markDone/snooze отправлял весь state включая до 10k history-записей (~500KB JSON) к каждому BrowserWindow. Теперь AppState (renderer-facing) без history, а PersistedState (internal) с историей. Renderer и так дёргал getHistory() отдельно, поведение не изменилось — только perf.
  • lib/icon.tsx: import * as Lucide (wildcard, ~500KB всех 1500+ иконок в bundle) → explicit named imports + ICON_MAP. В bundle только 18 ICON_CHOICES.
  • ChallengeEditor: multiplier клампится в UI до [0.5, 1000] (совпадает с validate.ts). Раньше save с 9999 молча отклонялся IPC-валидатором.

Fixed

  • atomicWrite spin-loop → async setTimeout. Раньше при retry на EBUSY/EPERM (антивирус, OneDrive) main process замораживался на 50/200/800ms × до 3 итераций ≈ секунда залипания UI. Сейчас async sleep — event-loop живёт. Аналогичный фикс в games/steam-launch-options.ts. Сохранён sync-вариант для flushNow в before-quit (там event-loop уже не работает).
  • before-quit дожидается stopGamesRegistry через e.preventDefault() + app.exit(0). Раньше GSI HTTP server не успевал closeAllConnections до exit, и следующий запуск получал EADDRINUSE на порту 4701 (TIME_WAIT) — GSI молча не работал.
  • IPC getState не мутирует кэш. Раньше state.settings.startWithWindows перезаписывалось напрямую, разъезжаясь с persisted-disk-значением до следующего mutation. Сейчас возвращается поверхностная копия.

0.5.4 — 2026-05-19

Обновление приложения теперь по-настоящему фоновое + почти моментальный рестарт в новую версию.

Changed

  • Скачивание апдейта — фоновое. Раньше клик «Скачать» блокировал кнопку (busy=true) до конца download'а (минуты на медленной сети). Теперь IPC updaterDownload — fire-and-forget, прогресс приходит через события. Пользователь сразу может уйти на Dashboard и продолжать упражнения, апдейт качается в фоне.
  • «Рестарт» — почти моментальный. quitAndInstall(true, true): isSilent=true — NSIS без UI установщика (~1-2 сек вместо ~5-10), isForceRunAfter=true — гарантия что приложение откроется после. Раньше показывался диалог установщика с прогрессом, теперь — только мгновение между закрытием и появлением новой версии.
  • Подсказка на экране скачивания: «можно закрыть это окно, продолжится в фоне». На downloaded-экране: «нажми Рестарт — приложение моментально откроется в новой версии».

0.5.3 — 2026-05-19

Полировка кастомного тайтлбара и размера окна.

Added

  • Maximize/Restore. Средняя кнопка тайтлбара (иконка квадрата) раньше была «спрятать в трей» — выглядела как нативная Windows maximize и сбивала с толку. Теперь это настоящий toggle на весь экран: иконка свапается SquareCopy в зависимости от состояния, aria-label локализован.
  • Double-click по тайтлбару тоже toggleMaximize — стандартный Windows-жест.
  • CLAUDE.md в корне — контекст проекта для будущих сессий Claude Code (стек, архитектура, команды, тех. долг).

Fixed

  • Drag-зона тайтлбара. Окно не двигалось, если хватать его рядом с кнопками свернуть/закрыть. Класс titlebar-nodrag стоял на обёртке кластера с flex-1 basis-0, поэтому пустое место слева от иконок тоже было no-drag. Перенесли no-drag на сами кнопки — теперь тащить можно отовсюду, кроме самих квадратиков.

Changed

  • Минимальный размер окна 900×600 → 1100×700. Гарантирует срабатывание Tailwind lg: (4 hero-stat в один ряд, heatmap и сетка упражнений помещаются без горизонтального скролла).

0.5.2 — 2026-05-19

Большая внутренняя итерация: тройной независимый аудит (~220 находок), закрыты топ-приоритеты. Тестов 53, ESLint и Prettier чистые, typecheck OK.

Added

  • Prettier + ESLint + EditorConfig. Конфиги, скрипты npm run format / format:check / lint, CI-готовые правила. Вся src/ единообразно отформатирована.
  • Error Boundary на двух уровнях: вокруг всего App и вокруг роутов. Крах одной страницы (например, malformed history в HistoryHeatmap) больше не блэнкит окно — показывается локализованный fallback с кнопкой «Попробовать снова». Stack trace только в dev.
  • IPC validation layer (src/main/validate.ts) — hand-rolled схемы для всех renderer-supplied payload (intervalMinutes ∈ [1,1440], reps ∈ [1,9999], multiplier ∈ [0,1000], string-cap 200 chars, enum-валидация для theme/lang/notify-mode/stat, regex для HH:MM, дедупликация quietHours.days). Compromised renderer больше не может засунуть reps: NaN или intervalMinutes: -1 в стор.
  • Schema migrations framework. __schemaVersion в persisted-state, MIGRATIONS map для будущих структурных правок.
  • Modal focus trap + focus restore + aria-labelledby. Tab/Shift-Tab больше не вываливаются на нижний слой; на закрытии фокус возвращается на триггер.
  • Sidebar mobile drawer: Esc закрывает, focus trap внутри, focus restore на гамбургер, role="dialog" + aria-modal.
  • Tray menu i18n — пункты меню следуют settings.language.
  • Bilingual heatmap. Title, легенда, weekday-лейблы и tooltip с плюрализацией (1 повтор / 2 повтора / 5 повторов) — всё через i18n. 7 новых ключей weekday.short.*.
  • CHANGELOG.md по формату Keep a Changelog.

Fixed

  • Critical: данные больше не теряются на corrupt JSON. Раньше catch → makeInitial() молча затирал упражнения/историю. Теперь файл уезжает в app-state.json.corrupt-<timestamp>.
  • Atomic write через .tmp + rename + retry на EBUSY/EPERM (антивирус, OneDrive). Раньше обрыв питания мог дать truncate.
  • HIGH security: GSI server теперь верифицирует auth.token через timingSafeEqual против per-install токена. Раньше эндпоинт был полностью неаутентифицирован — любой локальный процесс мог подделать match-end.
  • HIGH security: shell.openExternal allowlist — только http/https/mailto. Раньше file:/javascript:/steam: уходили в OS handler.
  • HIGH security: dev IPC simulateMatchEnd убран из production билдов (gate на !app.isPackaged + import.meta.env.MODE).
  • HIGH security: GSI server reject Origin/Sec-Fetch-Site — блокирует CSRF от browser-вкладок. Body cap 256 KB (OOM-вектор закрыт). Require application/json. Generic 400 без error-echo.
  • isQuietAt wrap-around + day filter. С 22:00 → 07:00, days=[Mon..Fri] теперь правильно проверяется день начала окна (старт Fri 22:00 → активно ночью Sat 02:00).
  • DST drift в history.ts. Календарная арифметика (setDate) вместо ms-арифметики — на границе DST дни больше не дублируются.
  • Scheduler: broadcastState() после fire, защита от двойной регистрации powerMonitor listeners.
  • Settings IPC chatter. QuietTimesRow держит локальное состояние, IPC летит только на onBlur. Раньше скрабинг времени давал ~5 IPC, каждый переписывал app-state.json.
  • Dashboard «До следующего» показывает при паузе вместо обманчиво тикающего таймера.
  • HistoryHeatmap percentile-bucketing (p25/p50/p85) вместо относительной шкалы — outlier-день больше не схлопывает все нормальные дни в самый слабый бакет.
  • ReminderApp: Enter теперь корректно передаёт adjusted reps (раньше всегда planned). key={exercise.id+nextFireAt} сбрасывает степпер на новом fire. Степпер capped at 5× planned. Space не работает когда фокус на кнопке. Esc закрывает MatchSummary.
  • i18n.translate — split/join вместо regex (var-значения с регулярными метасимволами теперь интерполируются буквально).
  • icon.tsx lookup сужен до ICON_CHOICES — произвольное имя больше не зарезолвится в Lucide.default.
  • UpdaterCard NaN guard на download-progress (electron-updater даёт undefined в ранних событиях).
  • format.ts guard от NaN/Infinity в formatCountdown.
  • updateExercise/updateChallenge стрипают id из patch — рендер не может перезаписать identity.
  • clearHistory(undefined) теперь no-op (нужен явный boundary).

Removed

  • .gitea/workflows/*.yml — без runners оставляли queued runs. Релизим через release.ps1. has_actions на репо выключен.

0.5.1 — 2026-05-18

Fixed

  • Auto-update архитектурно переписан. Раньше publish.url включал ${version} и запекался в каждый билд — установленные копии видели только свой собственный релиз. Введён фиксированный …/releases/download/update-channel, который никогда не меняется.
  • Hourly auto-проверка работает в silent-режиме: транзитные сетевые ошибки (504, TLS drops) больше не показывают красный баннер «Ошибка проверки». Только ручной клик «Проверить» поднимает ошибку.
  • Boot-check ретраит 3 раза с backoff 30s/2m/5m.
  • В Up to date показывается «проверено N мин назад».
  • release.ps1 теперь публикует в три-четыре места одной командой: vX.Y.Z, update-channel, и переданные -BridgeTags для миграции пользователей со старых версий.
  • upload-release-assets.ps1 ретраит curl до 4 раз с backoff на 504 / TLS-сбрасывание; до ретрая проверяет, не залился ли файл на самом деле (Gitea часто принимает body, но таймаутит ответ).
  • Скрипты — ASCII-only (PS5.1 без BOM падает на em-dash).

Removed

  • .gitea/workflows/*.yml — Gitea Actions без настроенных runners оставляли queued runs в репозитории. Релизим через release.ps1.

0.5.0 — 2026-05-18

Added

  • История + стрики. Каждое выполненное упражнение пишется в app-state.json (cap 10k записей, trim oldest 10% на overflow). Heatmap-календарь 12 недель на Dashboard, ежедневный счётчик «сделано сегодня», серия дней подряд (с grace-периодом за вчера).
  • Тихие часы. Окно времени, в которое напоминания подавляются. Поддержка wrap-around (22:00 → 08:00) и фильтра по дням недели.
  • Частичное выполнение. Степпер /+ в окне напоминания: можно отметить «сделал 5 из 10», в историю запишется честное число.
  • README.md на русском — описание, фичи, установка, dev-команды, архитектура, стек.

Changed

  • markDone(id, actualReps?) принимает фактическое число повторений.

Tests

  • +18 тестов (5 для тихих часов, 13 для истории/стриков). Всего 51.

0.4.0 — 2026-05-17

Added

  • Английская локализация. Самописная i18n: плоский словарь ~200 ключей × 2 языка + хук useT() + плюрализация (CLDR rules для RU: one/few/many).
  • Селектор языка в Settings, переключение мгновенное.

[0.3.x] — 2026-05-17

Серия мелких релизов с дизайн-итерациями (Apple iOS / macOS aesthetic): шрифты Plus Jakarta Sans + Bricolage Grotesque, светлая/тёмная/системная тема, vibrancy sidebar, iOS-grouped lists, spring-анимации.

0.2.0 — 2026-05-16

Added

  • Dota 2 Game State Integration: локальный HTTP-сервер парсит callbacks от Steam, после Победа/Поражение показывает «причитающиеся» повторения (например 10 смертей × 3 = 30 приседаний).

[0.1.x] — 2026-05-15 .. 2026-05-16

Первые публичные сборки: ядро напоминаний (упражнения, интервалы, иконки), системный трей, автозапуск с Windows, native-уведомления, NSIS-инсталлятор, auto-update через electron-updater.