AnRil f0dc5b2cc3 feat: a11y + Error Boundary + IPC validation + schema migrations
Second pass through the audit punch-list. ESLint and Prettier now clean
(0 errors, 0 warnings), typecheck clean, 53 tests pass.

ACCESSIBILITY (Modal)
- Full focus trap: Tab/Shift-Tab cycle within the dialog and never
  escape to the underlying page.
- Focus restoration: closing returns focus to the trigger button.
- First focusable child is focused on open (skipping the X button).
- aria-labelledby links the dialog to its <h2> via useId().
- Close button's hardcoded "Закрыть" replaced with i18n key.

ERROR RECOVERY
- Add ErrorBoundary component (class — only way) with localized
  fallback and a "try again" reset button. Stack trace shown only in
  dev. Wrapped around the whole App + a nested boundary around the
  routed pages so a crash in one route doesn't blank the chrome.
- Module-level guard on subscribeToBackend so React 18 StrictMode's
  dev-mode double-mount doesn't subscribe twice.
- Loading placeholder is now blank (was hardcoded Russian "Загрузка…"
  that English users would see during initial hydration).

TRAY i18n
- 5 tray strings now follow the current settings.language. Falls back
  to Russian when the store isn't loaded yet or the lang is unknown.
- refreshMenu() called on settings.language change and on
  pauseAll/resumeAll so the pause label stays in sync with state.

IPC VALIDATION (src/main/validate.ts)
- Hand-rolled validators for every renderer-supplied payload:
  exercise input/patch, challenge input/patch, settings patch, id,
  actualReps, snoozeMinutes. Range-check numeric fields
  (intervalMinutes ∈ [1, 1440], reps ∈ [1, 9999], multiplier ∈ [0,
  1000], snooze ∈ [1, 1440]), cap string lengths at 200, restrict
  enums (theme/lang/notify-mode/stat) to known values, validate
  quietHours.from/to with HH:MM regex and dedup quietHours.days.
- Every ipcMain.handle for mutations now runs the validator first and
  returns null on rejection instead of pushing junk into the store.
  A compromised renderer can no longer corrupt persisted state via
  out-of-range numbers or wrong-type fields.

SCHEMA MIGRATIONS (src/main/store.ts)
- Add __schemaVersion field to persisted state with CURRENT = 1.
- MIGRATIONS map: { 0: (s) => s } as a no-op seed; future structural
  changes (e.g. quietHours shape revision) get a single explicit slot.
- runMigrations() applies migrations in order; coerce() normalises the
  result into a fully-formed AppState. Both first-write and every
  flush() persist the version field.

EXHAUSTIVE-DEPS WARNINGS
- Dashboard: memoise `exercises` so downstream useMemos don't fire on
  every parent render; gate the history fetch on exercises change
  instead of any state change.
- HistoryHeatmap: wrap `weeks` in useMemo so monthLabels' deps are
  stable.

LINT POLISH
- updater.ts: refactor a Prettier-vs-no-extra-semi conflict by
  extracting the cast into a local binding.
- Remove dead import of `Challenge` from ipc.ts (now imported via
  validators).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 23:21:27 +07:00
2026-05-16 13:43:29 +07:00
2026-05-16 13:43:29 +07:00
2026-05-16 13:43:29 +07:00

Laude — Exercise Reminder

Windows desktop приложение, которое напоминает делать упражнения во время работы за компьютером. Опционально подключается к Dota 2 и после каждого матча превращает статистику (смерти, убийства, ассисты) в количество повторений.

release tests platform

Что внутри

  • Гибкие напоминания — любое количество упражнений, интервал от минуты до часов, разные иконки.
  • История и стрики — heatmap-календарь активности, ежедневный счётчик, серия дней подряд.
  • Тихие часы — окно времени когда напоминания подавляются (например 22:00 → 08:00), с выбором дней недели.
  • Сделал частично — степпер /+ в окне напоминания: если ты сделал 5 из 10, в историю запишется честное число.
  • Игровая интеграция (Dota 2) — Game State Integration читает статистику матча, после Победа/Поражение показывает экран с «причитающимися» повторениями (например 10 смертей × 3 = 30 приседаний).
  • Apple-style интерфейс — Plus Jakarta Sans + Bricolage Grotesque, iOS-палитра, vibrancy sidebar, spring-анимации, светлая/тёмная/системная тема.
  • Два языка — русский и английский, переключение мгновенное.
  • Auto-update — приложение само скачивает новые версии из фиксированного update-channel (проверка каждый час, силент-ретрай при сетевых сбоях).

Скриншоты

TODO: вставить screenshots Dashboard / Reminder / Match summary (light + dark).

Установка

Скачай последний Exercise-Reminder-Setup-X.Y.Z.exe со страницы релизов и запусти. Установщик:

  • Создаёт ярлык на рабочем столе и в Пуске
  • Сохраняет настройки в %APPDATA%\Exercise Reminder\
  • При запуске поверх существующей инсталляции — обновляет, настройки сохраняются

Windows SmartScreen может предупредить «не доверено» — приложение не подписано code-signing сертификатом. Нажми ПодробнееВыполнить в любом случае.

Разработка

git clone https://xn--90adajar8af4h.xn--p1ai/git/AnRil/laude.git
cd laude
npm install
npm run dev

Полезные команды:

npm run typecheck       # tsc по main + renderer
npm run test            # vitest в watch-режиме
npm run test:run        # vitest один раз (для CI)
npm run build           # сборка без NSIS
npm run dist            # сборка + NSIS-инсталлятор → release/
npm run release -- -Bump patch    # bump версии + tag + push + upload в Gitea

Документ RELEASING.md описывает процесс выпуска новых версий.

Архитектура

  • Electron 33 — multi-process: main (Node/scheduler/GSI) + preload (contextBridge) + renderer (React)
  • Renderer — React 18, TypeScript 5, Vite 5, Tailwind 3, framer-motion, react-router, zustand
  • Persistence — единственный JSON-файл %APPDATA%\Exercise Reminder\app-state.json (debounced writes)
  • IPC — типизированные каналы через src/shared/ipc.ts, обёрнуто preload-ом
  • i18n — самописная микро-система: src/renderer/src/i18n/dict.ts (плоский словарь ~200 ключей × 2 языка) + хук useT()
  • Auto-updateelectron-updater с generic provider, манифест latest.yml лежит в Gitea release attachments
  • GSI Dota 2 — локальный HTTP-сервер слушает GameStateIntegration коллбэки от Steam, парсит match-end events

Тесты

src/shared/types.test.ts                (4)
src/shared/quiet-hours.test.ts          (5)
src/renderer/src/lib/format.test.ts     (8)
src/renderer/src/lib/history.test.ts   (13)
src/main/games/vdf.test.ts             (11)
src/renderer/src/i18n/i18n.test.ts     (10)
─────────────────────────────────────────
                                  51 ✓

Покрытие: чистые helpers (форматирование, история/стрики, тихие часы, парсер VDF для Steam-конфигов), i18n с плюрализацией для RU/EN, дефолты shared-типов.

Лицензия

Пока не указана. По умолчанию все права защищены. Если хочешь форк/использование — открой issue.

Stack

Description
Exercise reminder - Windows desktop app
Readme 1.7 MiB
2026-05-22 23:38:16 +07:00
Languages
TypeScript 93.5%
PowerShell 4.2%
CSS 1.5%
JavaScript 0.6%
HTML 0.2%