11 KiB
11 KiB
CLAUDE.md
Контекст проекта для Claude Code. Читается при старте каждой сессии.
TL;DR
Laude / Exercise Reminder — Windows desktop приложение на Electron 33, которое напоминает делать упражнения и опционально парсит статистику матчей Dota 2 (через GSI) в количество повторений. Текущая версия — 0.5.3. Один разработчик (AnRil), один remote — self-hosted Gitea.
Стек
- Runtime: Electron 33 (main + preload + renderer)
- 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 (53 теста, все зелёные)
- 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)
Архитектура (важное)
Процессы
- main (
src/main/) — Node, scheduler, GSI HTTP-сервер, IPC, окна, tray, updater, persistence - preload (
src/preload/index.ts) — contextBridge →window.api, dev-only методы вырезаны на проде (import.meta.env.MODE !== 'production') - renderer (
src/renderer/src/) — React, zustand-store, страницы Dashboard/Games/Settings/About, ReminderApp в отдельном окне
Persistence
- Единственный JSON-файл:
%APPDATA%\Exercise Reminder\app-state.json - Атомарная запись: tmp + rename + retry на EBUSY/EPERM (антивирус, OneDrive)
- Не теряет данные: corrupt JSON → quarantine в
app-state.json.corrupt-<ts>, не silent wipe - Schema migrations:
__schemaVersionполе +MIGRATIONS: Record<number, (s)=>s>map вsrc/main/store.ts - Debounced writes: pendingWrite с
.unref()
IPC
- Типизированные каналы —
src/shared/ipc.ts - Validation layer —
src/main/validate.ts(hand-rolled, без zod):intervalMinutes ∈ [1, 1440],reps ∈ [1, 9999],multiplier ∈ [0, 1000]- string cap 200 chars, enum-валидация для theme/lang/notify-mode/stat
- HH:MM regex для quietHours, dedup days
- Strip
idиз updateExercise/updateChallenge patch
- Dev-only:
dev:simulateMatchEndgated на!app.isPackaged
Auto-update (КРИТИЧНО)
- Фиксированный URL канала:
…/releases/download/update-channel/latest.yml— никогда не меняется - НЕ
…/releases/download/v${version}/…(старая схема ломалась: установленная копия видела только свой релиз) - Hourly silent auto-check (транзитные сетевые ошибки не показывают красный баннер; только ручной клик показывает ошибку)
- Boot-check: 3 ретрая с backoff 30s/2m/5m
lastCheckedAt→ UI «проверено N мин назад»- Релиз через
scripts/release.ps1публикует одной командой в:vX.Y.Z(постоянный архивный тег)update-channel(rolling — клиенты проверяют отсюда)- Опциональные
-BridgeTagsдля миграции старых пользователей
Безопасность
- GSI server (
src/main/games/gsi-server.ts): per-install token verify черезtimingSafeEqual, reject Origin/Sec-Fetch-Site (CSRF), 256KB body cap, requireapplication/json, generic 400 - shell.openExternal allowlist: только
http:/https:/mailto:(src/main/windows.ts) - will-navigate блокирует non-file:// и non-dev URL
- Modal focus trap + focus restore, aria-labelledby
Quiet hours
isQuietAt(time, settings)вsrc/shared/types.ts- Wrap-around (22:00 → 07:00) корректно — при wrap-active проверяется день начала окна
- Тесты в
src/shared/quiet-hours.test.ts
История / стрики
src/renderer/src/lib/history.ts— DST-safe черезshiftDays()(calendarsetDate, не ms-арифметика)- Cap 10k записей, trim oldest 10% на overflow
- HistoryHeatmap: percentile-based bucketing (p25/p50/p85), а не flat ratio (защищает от outlier-дней)
i18n
- Самописная микро-система:
src/renderer/src/i18n/dict.ts(плоский словарь ~200 ключей × 2 языка) - Хук
useT(), плюрализация CLDR rules для RU (one/few/many) - Интерполяция через split/join (не regex — защита от regex-инъекций в значениях var)
- Tray menu тоже локализован (
TRAY_STRINGSвsrc/main/tray.ts)
Команды
npm run dev # electron-vite dev
npm run typecheck # tsc по node + web
npm run test:run # vitest один раз
npm run lint # eslint --max-warnings 0
npm run format # prettier --write
npm run dist # сборка + NSIS installer → release/
# Релиз (всё в одном)
npm run release -- -Bump patch
# или -Bump minor / -Bump major / -Version 1.2.3
# опционально: -BridgeTags v0.4.0,v0.4.1
Скрипты релиза
scripts/release.ps1— bump → typecheck → test → build → tag → push → upload в Gitea (vX.Y.Z + update-channel + bridges)scripts/upload-release-assets.ps1— curl.exe с retry/backoff (15s/45s/2m/5m × 4) на 504/TLS, проверяет уже-залилось через list assets перед ретраем- PowerShell 5.1 gotchas:
- Default reads CP1251 → файлы скриптов ASCII-only, без em-dash/кириллицы в коде
Set-Content -Encoding utf8добавляет BOM → ломает PostCSS. Для UTF-8 без BOM использовать[System.IO.File]::WriteAllText+new UTF8Encoding($false)- Никогда
-iфлаги (rebase -i, add -i) — нет interactive input
Gitea remote
- URL:
https://xn--90adajar8af4h.xn--p1ai/git/AnRil/laude(Punycode дляпрезидент.рф) - User:
anril - Auth: см.
~/.claude/projects/.../memory/gitea_remote.md - Actions выключены (
has_actions: false) — релизим через PowerShell, runners не настроены .gitea/workflows/пустая (раньше там лежали yml → queued runs копились)
Файлы, которые часто правлю
| Файл | Что |
|---|---|
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/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 |
окно напоминания |
Тесты (53)
src/shared/types.test.ts (4)
src/shared/quiet-hours.test.ts (7)
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)
Покрываются: helpers, история/стрики (DST), тихие часы (wrap+filter), VDF-парсер Steam, i18n с плюрализацией, дефолты.
Технический долг (не для пользователя)
sandbox: trueна BrowserWindow — нужен smoke-тест preload в sandbox-режиме- Self-host Google Fonts (сейчас внешняя CSP-зависимость)
- ReminderApp race: первое напоминание может прийти без озвучки до загрузки settings
- Мажорные апдейты (React 18→19, Electron 33→42, Tailwind 3→4) — каждый ломающий, отдельная итерация
- Code-signing NSIS — ~$300/год, уберёт SmartScreen warning
- Скриншоты в README (есть TODO в самом README)
Стиль кода
- Prettier: semi:false, singleQuote, trailingComma:none, printWidth:80
- ESLint: eslint:recommended + ts + react + react-hooks (без style rules — это Prettier)
- TypeScript strict, никакого
anyв новом коде - Комментарии на русском там, где объясняют почему, не что
- Коммиты на русском, формат
тип(scope): кратко(feat/fix/docs/refactor/test/chore) - Co-Authored-By футер в коммитах от Claude
Управление контекстом
- Ужимать контекст при достижении 250k токенов — вызывать
/compact(или эквивалент) когда суммарный контекст подходит к 250 000 токенов. Не дожидаться authentic переполнения и автоматического сжатия от рантайма — сделать это контролируемо, чтобы важный контекст (открытые правки, недокоммиченные решения, текущая ветка задачи) попал в summary, а не выпал.
Чего НЕ делать
- Не пушить в
update-channelруками — только черезrelease.ps1 - Не добавлять
.gitea/workflows/*.yml— has_actions выключен, runs зависнут - Не использовать regex в i18n-интерполяции — только split/join
- Не silent wipe corrupt JSON — quarantine с timestamp
- Не возвращать ms-арифметику в history.ts — DST сломается
- Не убирать validation layer из IPC — compromised renderer может засунуть NaN/негативы
- Не амендить коммиты без явной просьбы пользователя