# Changelog Все заметные изменения проекта документируются здесь. Формат основан на [Keep a Changelog](https://keepachangelog.com/ru/1.1.0/), проект следует [Semantic Versioning](https://semver.org/lang/ru/). ## [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.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). - `` синхронизируется с `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 на весь экран: иконка свапается `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, `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-`. - **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. [Unreleased]: https://xn--90adajar8af4h.xn--p1ai/git/AnRil/laude/compare/v0.5.6...HEAD [0.5.6]: https://xn--90adajar8af4h.xn--p1ai/git/AnRil/laude/releases/tag/v0.5.6 [0.5.5]: https://xn--90adajar8af4h.xn--p1ai/git/AnRil/laude/releases/tag/v0.5.5 [0.5.4]: https://xn--90adajar8af4h.xn--p1ai/git/AnRil/laude/releases/tag/v0.5.4 [0.5.3]: https://xn--90adajar8af4h.xn--p1ai/git/AnRil/laude/releases/tag/v0.5.3 [0.5.2]: https://xn--90adajar8af4h.xn--p1ai/git/AnRil/laude/releases/tag/v0.5.2 [0.5.1]: https://xn--90adajar8af4h.xn--p1ai/git/AnRil/laude/releases/tag/v0.5.1 [0.5.0]: https://xn--90adajar8af4h.xn--p1ai/git/AnRil/laude/releases/tag/v0.5.0 [0.4.0]: https://xn--90adajar8af4h.xn--p1ai/git/AnRil/laude/releases/tag/v0.4.0 [0.2.0]: https://xn--90adajar8af4h.xn--p1ai/git/AnRil/laude/releases/tag/v0.2.0