Files
laude/CHANGELOG.md
2026-05-22 14:00:28 +07:00

356 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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).
- `<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 на
весь экран: иконка свапается `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-<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.
[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