Commit Graph

11 Commits

Author SHA1 Message Date
Codex
ffe80b62c4 fix: harden reminders and state handling 2026-06-06 02:27:04 +07:00
AnRil
bef733a877 feat(meals): вкладка «Питание» — напоминания о еде по времени суток
Новая модель Meal — напоминание по настенным часам (time HH:MM + дни недели),
в отличие от interval-based Exercise. Отдельная вкладка «Питание» с пресетами
быстрого добавления (Завтрак/Обед/Ужин/Перекус).

- shared: тип Meal, meals в AppState, nextMealOccurrence (DST-safe), SAMPLE_MEALS,
  MEAL_PRESETS; IPC-каналы meal:* + evtFireMeal
- main: валидация (строгая HH:MM-проверка диапазона), store-мутаторы с пересчётом
  nextFireAt, scheduler.checkDueMeals (гейт только globalEnabled, grace-окно 120с,
  игнор тихих часов/ВКС), notifications.fireMealReminder, IPC-хендлеры
- renderer: вкладка Meals + MealEditor (время/дни/иконка), MealReminder в окне
  напоминания (Поел/Отложить, TTS), пункт в Sidebar, маршрут, i18n RU/EN, иконки
  UtensilsCrossed/Soup
- persistence: meals additive (без bump схемы — старые state'ы получают [])
- +24 теста (203 -> 227): nextMealOccurrence, валидаторы приёмов пищи,
  scheduler meal-gating (вкл/выкл, grace, игнор тихих часов)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 23:45:34 +07:00
AnRil
0c813c3ac8 fix+test: автономные правки после ревью v0.5.7
Bug — Heatmap/streak/achievements не обновлялись после markDone/
    markChallengeDone. Регресс из Sprint C (история выделена из
    state-broadcast). Корень: store мутирует Exercise.lastDoneAt
    in-place → state.exercises ref не меняется → useEffect([exercises])
    не fires → Dashboard не перетягивает history.
    Фикс: новый event IPC.evtHistoryChanged + broadcastHistoryChanged().
    Триггерится после markDone/snooze/skip/markChallengeDone/
    clearHistory/import. Dashboard.useEffect подписывается через
    onHistoryChanged.

Settings → AboutCard теперь показывает текущую версию приложения
    (раньше была только кнопка «Что нового»). Версия через
    IPC.getAppVersion.

Tests:
    +6 для repsDoneTodayForExercise — match-challenges, snapshot,
       deleted-exercise fallback, ignore skip/snooze.
    +2 для dailyReps с новыми snapshot-полями (match-challenges
       и deleted exercises).
    +6 для unseenVersions + RELEASE_NOTES контракт.
    +7 для adjustNextFireAt (адаптивный шедулер): малая история,
       плохой/хороший час, MAX_SHIFT_HOURS, фильтр по упражнению,
       30-day window.
    Итого 135 → 159 (+24).

Грепнул src/ на стейл-references к removed setPaused/isPaused/
    `let paused` — чисто. Sprint C-D refactor завершён без residue.
2026-05-22 23:22:34 +07:00
AnRil
9c989612fe fix(P1): delete-confirm, daily-goal closed UI, meeting indicator, modal-confirm
P1 #4 — ConfirmModal (новый src/renderer/src/components/ui/ConfirmModal.tsx)
    с iOS-стилем + focus-trap (через Modal). Delete упражнения в Dashboard
    теперь спрашивает «Удалить упражнение?» с destructive-кнопкой.

P1 #5 — Daily goal closed UI. ExerciseCard принимает doneToday prop
    и при `done >= dailyGoal` показывает «Цель закрыта · 100/100»
    вместо запутанного «25ч 13м» countdown'а. Цвет — success-зелёный.

P1 #6 — Meeting auto-pause indicator. Новый IPC.getMeetingActive +
    evtMeetingChanged event. meeting-detect broadcast'ит изменения
    состояния. Dashboard показывает info-баннер «Не дёргаем — ты на
    встрече» когда meetingAutoPause включён и хотя бы один meeting
    процесс запущен.

P1 #7 — Native window.confirm() заменён на ConfirmModal в Settings
    DataCard для restore-операции. Теперь iOS-style с destructive
    confirm-кнопкой и focus-trap'ом.

Заодно P2 #8: Brain-иконка-badge на ExerciseCard для adaptive
    упражнений — пользователь видит почему «Next» не строго равен
    intervalMinutes.
P2 #12: dailyReps/dailyRepsRange/totalDoneReps/repsDoneTodayForExercise
    используют entry.reps как fallback — heatmap не теряет данные
    после удаления упражнения.
2026-05-22 15:06:25 +07:00
AnRil
5a9ec04ba8 feat(whatsnew): экран «Что нового» — автопоказ после апдейта + кнопка в Settings
- src/shared/release-notes.ts — статический реестр заметок per-version
  (RU + EN), с тегами new/fix/security/perf для tint'а иконок.
- Settings.lastSeenVersion — версия, для которой пользователь видел
  модалку. Валидатор регэксом /^\d+\.\d+\.\d+(-[\w.]+)?$/.
- IPC.getAppVersion → app.getVersion() для renderer'а.
- WhatsNewModal — список пунктов с цветовыми иконками. Footer-кнопка
  «Понятно» / «Got it».
- App.tsx: после hydrate смотрит lastSeenVersion → current. Если
  расходятся и есть пропущенные заметки → автопоказ. На первой
  записи (lastSeenVersion === undefined) — тихо записываем, без
  модалки, чтобы не бить нового пользователя CHANGELOG'ом.
- Settings → раздел «О приложении» → кнопка «Открыть» показывает
  модалку с заметками всех релизов.
2026-05-22 13:59:29 +07:00
AnRil
72e54c579d feat(#9): export/import состояния — backup в JSON и восстановление 2026-05-22 13:33:38 +07:00
AnRil
a41dce511b chore: sprint A — мелкая полировка
#15 a11y: <html lang> синхронизируется с settings.language через
    ThemeProvider — screen-readers больше не читают EN-текст с
    русским акцентом и наоборот.
#14 dev:simulateMatchEnd channel вынесен в IPC enum
    (IPC.devSimulateMatchEnd) — main/preload не разойдутся в hardcoded
    строках.
#34 ChallengeEditor: multiplier клампится к [0.5, 1000] (max="1000",
    Math.min(1000, ...)). Совпадает с validate.ts — раньше save с 9999
    молча отклонялся IPC, теперь UI не даёт ввести.
#28 package.json: добавлен `test:coverage` script.
2026-05-22 01:13:01 +07:00
AnRil
e96ca06587 feat(window): maximize toggle + drag-zone fix + minWidth bump
- Средняя кнопка тайтлбара теперь toggle maximize/restore (была
  hide-to-tray, но иконка Square вводила в заблуждение — выглядит
  как нативная maximize). Double-click по тайтлбару тоже работает.
- Иконка свапается Square ↔ Copy в зависимости от max-state,
  aria-label локализован (titlebar.maximize_aria / restore_aria).
- Новый IPC: toggleMaximizeMain, isMaximizedMain (invoke),
  evtMaximizeChanged (event main → renderer на maximize/unmaximize).
- Фикс drag-зоны: titlebar-nodrag перенесён с обёртки правого
  кластера на сами кнопки. Из-за flex-1 basis-0 пустое место слева
  от кнопок раньше было no-drag — окно нельзя было ухватить рядом.
- minWidth/minHeight окна 900x600 → 1100x700, чтобы Tailwind lg:
  всегда срабатывал (4 hero-stat в один ряд, heatmap без скролла).
- CLAUDE.md: контекст проекта для будущих сессий Claude Code
  (стек, архитектура, команды, релиз, тех. долг, чего не делать).
2026-05-19 17:52:54 +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
92e15e69a3 feat: auto-update, тесты и CI/CD
Some checks failed
CI / Typecheck + Tests (push) Has been cancelled
CI / Build (Windows) (push) Has been cancelled
Полная автоматизация релизного цикла.

== Auto-update (electron-updater) ==
- src/main/updater.ts — обёртка над autoUpdater с дискриминированным
  UpdaterStatus union и broadcast через IPC. autoDownload=false,
  пользователь сам жмёт «Скачать». allowDowngrade=false. Проверка
  каждые 6 часов, первая через 5с после старта.
- В dev-режиме (app.isPackaged=false) статус сразу становится
  'unsupported' с пояснением — никаких exceptions из updater'а.
- build.publish в package.json: provider=generic, url указывает на
  Gitea release assets конкретной версии.
- src/main/ipc.ts: 4 новых канала — status/check/download/install.
- src/preload: API window.api.updater* + onUpdaterStatus.
- src/renderer/src/components/UpdaterCard.tsx: HUD-карточка в Settings
  с состояниями idle/checking/available/downloading/downloaded/error,
  прогресс-бар с скоростью в МБ/с.

== Тесты (vitest) ==
- vitest.config.ts с алиасами @shared / @renderer
- 23 теста, все зелёные:
  * format.test.ts — formatCountdown, formatInterval (8 cases)
  * vdf.test.ts — parseVdf / stringifyVdf / round-trip (11 cases)
  * types.test.ts — DEFAULT_SETTINGS, SAMPLE_EXERCISES sanity (4)
- npm scripts: test (watch), test:run (CI)

== CI/CD (Gitea Actions) ==
- .gitea/workflows/ci.yml — на push/PR: typecheck + тесты + smoke-сборка
- .gitea/workflows/release.yml — на тег v*.*.*: сборка NSIS + Gitea release

== Локальный релизный скрипт ==
- scripts/release.ps1 — один скрипт от бампа версии до публикации
  через Gitea API (params: -Bump patch/minor/major, -Version, -DryRun)
- npm run release — обёртка
- RELEASING.md — полная инструкция

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