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>
This commit is contained in:
AnRil
2026-06-03 23:45:34 +07:00
parent 0cace2975d
commit bef733a877
22 changed files with 1292 additions and 26 deletions

View File

@@ -12,7 +12,7 @@
- **Build**: electron-vite 2 + Vite 5 + electron-builder 25 (NSIS, x64 only)
- **UI**: React 18 + TypeScript 5 + Tailwind 3 + framer-motion + react-router (HashRouter) + zustand 5
- **Auto-update**: electron-updater 6, generic provider, фиксированный канал
- **Тесты**: Vitest 4 (203 теста, все зелёные)
- **Тесты**: Vitest 4 (227 тестов, все зелёные)
- **Lint/format**: ESLint 8 (flat-ish .eslintrc.cjs) + Prettier 3 + EditorConfig
- **Иконки**: lucide-react (whitelisted lookup через `ICON_CHOICES`)
- **Шрифты**: Plus Jakarta Sans, Bricolage Grotesque, JetBrains Mono (Google Fonts CDN)
@@ -68,6 +68,20 @@
- Wrap-around (22:00 → 07:00) корректно — при wrap-active проверяется день *начала* окна
- Тесты в `src/shared/quiet-hours.test.ts`
### Питание / приёмы пищи (вкладка «Питание»)
- **Отдельная модель `Meal`** (`src/shared/types.ts`): напоминание ПО ВРЕМЕНИ СУТОК
(`time` HH:MM + `days` weekdays), в отличие от interval-based `Exercise`
- `nextMealOccurrence(time, days, fromMs)` — следующее срабатывание, календарная
арифметика (DST-safe, как history.ts). Тесты в `src/shared/meals.test.ts`
- Scheduler `checkDueMeals()` (`src/main/scheduler.ts`): гейтит **только**
`globalEnabled` (НЕ тихие часы / НЕ ВКС — время задано пользователем явно).
Grace-окно `MEAL_GRACE_MS=120s`: приём, пропущенный давно (сон/выкл), тихо
переносится без срабатывания, чтобы не вывалить пачку напоминаний разом
- Окно напоминания: `evtFireMeal``MealReminder` в `ReminderApp.tsx` (зелёный
акцент `bg-success`, кнопки «Поел» / «Отложить»)
- Пресеты быстрого добавления — `MEAL_PRESETS` (имена через i18n-ключи)
- Время валидируется строго (`validHHMM` в validate.ts — диапазон, не только форма)
### История / стрики
- `src/renderer/src/lib/history.ts` — DST-safe через `shiftDays()` (calendar `setDate`, не ms-арифметика)
- Cap 10k записей, trim oldest 10% на overflow
@@ -119,38 +133,41 @@ npm run release -- -Bump patch
| `package.json` | version, publish.url, scripts, deps |
| `src/main/store.ts` | persistence, migrations, validation, atomic writes |
| `src/main/ipc.ts` | IPC handlers с валидацией |
| `src/main/scheduler.ts` | таймеры упражнений, powerMonitor |
| `src/main/scheduler.ts` | таймеры упражнений (interval) + приёмы пищи (clock-time), powerMonitor |
| `src/main/games/dota2.ts` + `gsi-server.ts` | GSI приём матчей |
| `src/main/updater.ts` | auto-update logic, silent retries |
| `src/shared/types.ts` | shared типы, дефолты, isQuietAt |
| `src/shared/ipc.ts` | IPC channel types |
| `src/renderer/src/i18n/dict.ts` | словари |
| `src/renderer/src/pages/Dashboard.tsx` | главная |
| `src/renderer/src/ReminderApp.tsx` | окно напоминания |
| `src/renderer/src/pages/Meals.tsx` + `components/MealEditor.tsx` | вкладка «Питание» |
| `src/renderer/src/ReminderApp.tsx` | окно напоминания (упражнение / еда / матч) |
## Тесты (203)
## Тесты (227)
```
src/main/validate.test.ts (68)
src/main/validate.test.ts (78)
src/renderer/src/lib/history.test.ts (31)
src/renderer/src/i18n/i18n.test.ts (15)
src/renderer/src/lib/format.test.ts (14)
src/main/scheduler.test.ts (13) ← main: gating + приёмы пищи
src/main/games/vdf.test.ts (11)
src/main/store.test.ts (10) ← main: миграции/карантин/cap
src/renderer/src/lib/achievements.test.ts (10)
src/shared/release-notes.test.ts (9)
src/main/scheduler.test.ts (8) ← main: gating-логика
src/shared/meals.test.ts (8) ← nextMealOccurrence (DST-safe)
src/main/meeting-detect.test.ts (7) ← main: детект ВКС + кэш/timeout
src/shared/quiet-hours.test.ts (7)
src/main/adaptive.test.ts (6)
src/shared/types.test.ts (4)
src/renderer/src/lib/icon-choices.test.ts (3)
src/renderer/src/lib/icon-choices.test.ts (4)
```
Покрываются: IPC-валидация, persistence (миграции/карантин/cap), scheduler-gating
(тихие часы/ВКС/daily-goal), детект ВКС (мок child_process), helpers, история/стрики
(DST), тихие часы (wrap+filter), VDF-парсер Steam, достижения, i18n с плюрализацией,
дефолты.
Покрываются: IPC-валидация (упражнения/челленджи/приёмы пищи), persistence
(миграции/карантин/cap), scheduler-gating (тихие часы/ВКС/daily-goal), планирование
приёмов пищи по времени суток (DST-safe, grace-окно, игнор тихих часов), детект ВКС
(мок child_process), helpers, история/стрики (DST), тихие часы (wrap+filter),
VDF-парсер Steam, достижения, i18n с плюрализацией, дефолты.
Паттерн для main-тестов: `vi.mock('electron'|'./store'|'node:child_process')` +
`vi.resetModules()` + dynamic import (сброс module-level состояния между тестами).