22 KiB
Changelog
Все заметные изменения проекта документируются здесь. Формат основан на Keep a Changelog, проект следует Semantic Versioning.
Unreleased
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.comorigins (шрифты теперь 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:simulateMatchEndchannel вынесен в IPC enum (IPC.devSimulateMatchEnd).test:coveragenpm 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
atomicWritespin-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'а (минуты на медленной сети). Теперь IPCupdaterDownload— 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 на
весь экран: иконка свапается
Square↔Copyв зависимости от состояния, 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,MIGRATIONSmap для будущих структурных правок. - 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.openExternalallowlist — только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-вектор закрыт). Requireapplication/json. Generic 400 без error-echo. isQuietAtwrap-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, защита от двойной регистрацииpowerMonitorlisteners. - 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.tsxlookup сужен доICON_CHOICES— произвольное имя больше не зарезолвится вLucide.default.- UpdaterCard NaN guard на download-progress (electron-updater даёт undefined в ранних событиях).
format.tsguard от 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.