Commit Graph

5 Commits

Author SHA1 Message Date
AnRil
81481f2131 feat(#5): авто-пауза напоминаний во время ВКС (Zoom/Teams/Discord/Webex) 2026-05-22 13:48:42 +07:00
AnRil
a6ae931461 feat(#12): дневная цель — soft cap reps/день, после которого упражнение умолкает 2026-05-22 13:45:37 +07:00
AnRil
f3367e09de chore+fix: repo hygiene, code-review fixes, audit cleanup
Three independent code reviews + a security audit produced ~200 findings.
This commit lands the high-impact subset. Tests pass (53), typecheck
clean, eslint clean (3 minor exhaustive-deps warnings left).

REPO HYGIENE
- Add .editorconfig, .prettierrc.json, .prettierignore.
- Add ESLint flat config (.eslintrc.cjs) — correctness-focused, no style
  rules (Prettier owns formatting).
- Add `format` / `format:check` / `lint` npm scripts.
- Add CHANGELOG.md (Keep a Changelog format, back-filled to 0.1.x).
- Reformat all source via Prettier so future diffs stay small.

DATA SAFETY (src/main/store.ts)
- Atomic write (tmp + rename) with retry on transient EBUSY/EPERM —
  was non-atomic writeFileSync, vulnerable to truncation on power loss.
- On corrupt JSON, rename to `app-state.json.corrupt-<ts>` instead of
  silently overwriting the user's exercises/history with defaults.
- Validate parsed shape before merging — reject arrays/scalars where
  objects expected; per-field array checks.
- Strip `id` from incoming patches in updateExercise/updateChallenge —
  a runtime caller (IPC) could otherwise smuggle id changes through.
- clearHistory now refuses an unbounded wipe (no beforeTs => no-op);
  callers must pass an explicit boundary.
- unref() the debounce timer so it doesn't keep the event loop alive.

SECURITY (src/main/*)
- gsi-server: hard 256 KB body cap (was unbounded — local OOM vector),
  reject any Origin/Sec-Fetch-Site header (blocks browser CSRF from
  visited pages), require application/json Content-Type, generic 400
  on parse error (no error string echo to client), closeAllConnections
  + async close on stop.
- dota2: validate auth.token from payload with timingSafeEqual against
  the per-install token — was unauthenticated, any local process could
  forge match-end events. Narrow object shape before spread-merge to
  avoid throws on hostile payloads like {player:"x"}. Reset latest /
  prevState after match_end so the next match starts clean.
- ipc: gate `dev:simulateMatchEnd` registration behind `!app.isPackaged`
  so it does not exist in shipped builds.
- preload: gate the matching `simulateMatchEnd` export behind
  `import.meta.env.MODE !== 'production'` so the bundler dead-code-
  eliminates it from the production preload bundle.
- windows: shell.openExternal allowlist (http/https/mailto only) — was
  forwarding any URL, including file:/javascript:/custom URI handlers
  (some Windows handlers have been RCE vectors). will-navigate blocks
  navigation to anywhere except file:// or the dev URL.

CORRECTNESS (src/main/* + src/shared/*)
- shared/types.ts isQuietAt: fix wrap-around + day-of-week filter.
  With from=22:00 to=07:00 days=[Mon..Fri], the window started THE
  PREVIOUS DAY when we're in the AM half — old code checked today's
  day-of-week and got the wrong answer Sat 02:00 and Mon 01:00. Now
  the filter is evaluated against the window's START day. Also reject
  malformed HH:MM strings instead of producing NaN.
- scheduler: call broadcastState() after firing exercises so the
  renderer's Dashboard/Exercises pages don't show stale nextFireAt
  until the next state-changing IPC. Guard powerMonitor listeners
  against double-registration on dev hot-reload.
- dota2: fix `launchOptionStatus = steamRunning ? 'queued' : 'queued'`
  tautology — both branches now correctly read 'queued'.
- steam-launch-options: replace `require('node:fs')` inside atomicWrite
  with the top-level import; retry on transient EBUSY/EPERM.

CORRECTNESS (src/renderer/*)
- lib/history.ts: replace `today.getTime() - i * MS_DAY` arithmetic
  with `setDate(date - i)` calendar arithmetic in dailyRepsRange and
  currentStreak — DST transitions shift epoch math by ±1h and cause
  dayKey() to emit duplicate or missing days at the boundary.
- lib/icon.tsx: restrict name lookup to ICON_CHOICES set — an arbitrary
  string from a corrupted state file could otherwise resolve to
  unrelated Lucide exports and crash the renderer.
- lib/format.ts: guard formatCountdown against NaN/Infinity.
- i18n/index.ts: replace regex-based interpolation with split/join so
  variable values containing regex metacharacters interpolate
  literally; warn in dev on missing keys; clamp pluralRu(-N) via abs.
- ReminderApp: keyboard shortcuts moved INTO ExerciseReminder so Enter
  respects the stepper's `adjusted` flag (was always passing planned
  reps). Stepper capped at 5× planned. Don't hijack Space when a
  button is focused. `key={exercise.id+nextFireAt}` forces a fresh
  component for back-to-back reminders so stepper state resets. Match
  summary view gets Esc-to-close. Functional setMode in onMarkDone
  avoids races against stale `mode.done`.
- UpdaterCard: guard against NaN/Infinity in download-progress events
  (electron-updater fires early events with undefined fields).
- Games: gate DevPanel behind `import.meta.env.DEV` in addition to the
  main-side IPC gate, and narrow the `simulateMatchEnd` access.
- Add aria-labels for the +/- stepper buttons (i18n keys added).

TESTS
- +2 quiet-hours tests covering wrap-around + day-filter combo and
  malformed HH:MM fallback. Total 53 passing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 23:04:49 +07:00
AnRil
c9d4fc237e feat(v0.5.0): history + streak + heatmap, quiet hours, partial reps, README
Some checks failed
CI / Typecheck + Tests (push) Has been cancelled
CI / Build (Windows) (push) Has been cancelled
Release / Build installer + publish release (push) Has been cancelled
== История и стрики (#1) ==
- HistoryEntry { ts, exerciseId, action: done|skip|snooze, actualReps? }
  персистится в app-state.json, лимит 10k записей (~3 года), trim oldest 10%
- markDone/snooze/skip пишут в историю; markDone принимает optional actualReps
- IPC: getHistory(sinceMs?), clearHistory(beforeTs?) + preload bindings
- Renderer helpers (src/renderer/src/lib/history.ts):
  * dayKey(ts) — YYYY-MM-DD local
  * dailyReps(entries, exs, dayKey) — суммирует actualReps || planned
  * dailyRepsRange(entries, exs, days) — для heatmap, заполняет gaps нулями
  * currentStreak(entries) — consecutive days, today или yesterday (grace)
- Dashboard теперь 4 hero-карточки: Today (повторов за день) / Streak
  (дней подряд) / Next / Tracking
- Новый компонент HistoryHeatmap — GitHub-style 12-недельный календарь
  с 5 интенсивностями, локализованными подписями дней/месяцев

== Тихие часы (#2) ==
- shared/types.ts: QuietHours { enabled, from, to, days[] } + isQuietAt()
  helper с правильной обработкой wrap-around окон (22:00→08:00)
- DEFAULT_SETTINGS.quietHours = disabled, 22:00→08:00, все дни
- main/scheduler.ts: проверка isQuietAt перед fire; deferred fires
  поднимаются после окончания окна
- Settings UI: новая секция "Тихие часы" с toggle, time-pickers,
  day-of-week pills

== Сделал частично (#3) ==
- ReminderApp: stepper [−][число][+] вокруг счётчика повторов
- При adjusted (actualReps !== exercise.reps) число подсвечивается accent
  и появляется подпись "Засчитаем X из Y"
- markDone передаёт actualReps только если юзер реально изменил —
  иначе undefined чтобы история фиксировала планируемое значение чисто

== README.md (#4) ==
- Описание, фичи, скриншоты (TODO-плейсхолдер), установка, dev-команды,
  архитектура, тесты, stack, ссылка на RELEASING.md
- Бэйджи version / tests / platform

== i18n ==
- ~14 новых ключей × 2 языка: dashboard.stat.today_done, streak,
  settings.quiet.* (3 row'а), reminder.partial

== Тесты — 51 (было 33) ==
- shared/quiet-hours.test.ts (5): disabled, same-day, wrap-around,
  day filtering, zero-length
- renderer/lib/history.test.ts (13): dayKey, dailyReps (planned vs
  actual vs ignore non-done), currentStreak (empty, today gap,
  consecutive, yesterday grace, multi-entry same day), dailyRepsRange

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 12:41:13 +07:00
AnRil
688a86b611 Initial commit 2026-05-16 13:43:29 +07:00