Новая модель 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>
73 lines
1.5 KiB
TypeScript
73 lines
1.5 KiB
TypeScript
// Explicit-named imports — НЕ wildcard. Wildcard `* as Lucide` ломает
|
||
// tree-shaking: в bundle попадает вся библиотека (~500KB minified, 1500+
|
||
// иконок). Сейчас в bundle только 18 ICON_CHOICES.
|
||
import {
|
||
Activity,
|
||
Dumbbell,
|
||
StretchHorizontal,
|
||
PersonStanding,
|
||
Heart,
|
||
Footprints,
|
||
Hand,
|
||
Eye,
|
||
Brain,
|
||
Bike,
|
||
Waves,
|
||
Wind,
|
||
Sun,
|
||
Coffee,
|
||
Apple,
|
||
GlassWater,
|
||
BookOpen,
|
||
Sparkles,
|
||
UtensilsCrossed,
|
||
Soup
|
||
} from 'lucide-react'
|
||
import type { LucideProps } from 'lucide-react'
|
||
import { ICON_CHOICES, type IconName } from './icon-choices'
|
||
|
||
export { ICON_CHOICES, type IconName }
|
||
|
||
const ICON_MAP: Record<IconName, React.ComponentType<LucideProps>> = {
|
||
Activity,
|
||
Dumbbell,
|
||
StretchHorizontal,
|
||
PersonStanding,
|
||
Heart,
|
||
Footprints,
|
||
Hand,
|
||
Eye,
|
||
Brain,
|
||
Bike,
|
||
Waves,
|
||
Wind,
|
||
Sun,
|
||
Coffee,
|
||
Apple,
|
||
GlassWater,
|
||
BookOpen,
|
||
Sparkles,
|
||
UtensilsCrossed,
|
||
Soup
|
||
}
|
||
|
||
/**
|
||
* Render a Lucide icon by name. Restricted to the curated ICON_CHOICES set —
|
||
* an arbitrary string from a corrupted state file could otherwise resolve to
|
||
* unrelated Lucide exports (`default`, `createLucideIcon`, etc.) and crash
|
||
* the renderer.
|
||
*/
|
||
export function Icon({
|
||
name,
|
||
...props
|
||
}: { name: string } & LucideProps): JSX.Element {
|
||
const Cmp = ICON_MAP[name as IconName]
|
||
if (!Cmp) {
|
||
if (import.meta.env.DEV) {
|
||
console.warn(`[Icon] unknown icon name "${name}" — falling back`)
|
||
}
|
||
return <Activity {...props} />
|
||
}
|
||
return <Cmp {...props} />
|
||
}
|