diff --git a/CHANGELOG.md b/CHANGELOG.md index a2b3753..0bf75cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,92 @@ ## [Unreleased] +## [0.5.2] — 2026-05-19 + +Большая внутренняя итерация: тройной независимый аудит (~220 находок), +закрыты топ-приоритеты. Тестов 53, ESLint и Prettier чистые, typecheck OK. + ### Added -- Prettier + ESLint конфиги, скрипты `npm run format` / `npm run lint`. -- `.editorconfig` для единообразного оформления между редакторами. +- **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 diff --git a/README.md b/README.md index 5b60f58..f666d2f 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Windows desktop приложение, которое напоминает делать упражнения во время работы за компьютером. Опционально подключается к Dota 2 и после каждого матча превращает статистику (смерти, убийства, ассисты) в количество повторений. -[![release](https://img.shields.io/badge/release-v0.5.1-orange)](https://xn--90adajar8af4h.xn--p1ai/git/AnRil/laude/releases/latest) +[![release](https://img.shields.io/badge/release-v0.5.2-orange)](https://xn--90adajar8af4h.xn--p1ai/git/AnRil/laude/releases/latest) [![tests](https://img.shields.io/badge/tests-51%20passing-green)]() [![platform](https://img.shields.io/badge/platform-Windows%2010%2F11-blue)]()