85897aa7dc8553711176756b0b044d55b8fc579e
28 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
85897aa7dc |
fix(a11y+i18n): heatmap/weekdays via dict, Sidebar focus trap, debounce time-picker
Third pass through the audit list. Tests still 53 passing, typecheck and
ESLint clean.
i18n — finish removing hardcoded localised strings from components
- Add 7 weekday short labels (weekday.short.0..6, index = Date.getDay()).
- Settings QuietDaysRow + HistoryHeatmap now pull weekday labels from
the dict instead of inline ru/en arrays.
- Heatmap title, legend (Less/More), and per-cell rep tooltip are now
i18n keys; the tooltip uses translateN with proper Russian plurals
(1 повтор / 2 повтора / 5 повторов).
- New aria labels: sidebar.aria.nav, exercise.aria.toggle.
- HistoryHeatmap no longer takes a `lang` prop — pulls language from
useT() like every other component.
Heatmap intensity scaling
- Bucket thresholds now percentile-based (p25/p50/p85 over non-zero days)
rather than a flat ratio against the single max. A 200-rep "catch up"
day no longer collapses every normal 10-rep day into the lowest bucket.
Sidebar mobile drawer
- Esc closes the drawer.
- Tab/Shift-Tab trap inside the drawer.
- Focus restores to the hamburger button on close.
- Drawer gets role="dialog" + aria-modal="true" + aria-label.
- Backdrop gets aria-hidden so screen readers skip the scrim.
Settings — stop IPC chatter on time picker
- QuietTimesRow mirrors `from`/`to` into local state and only emits an
updateSettings IPC on blur (or when the local value matches HH:MM and
differs from the current setting). Was firing ~5 IPCs while the user
scrubbed time inputs, each rewriting app-state.json.
- QuietDaysRow uses a numeric sort comparator instead of default lexical.
Dashboard polish
- "Until next reminder" hero stat now shows "—" when paused instead of
continuing to tick down a misleading countdown.
ExerciseCard
- Switch aria-label was t('btn.done') ("Готово") — wrong semantics.
Now reads "Toggle exercise X" via new i18n key.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
f0dc5b2cc3 |
feat: a11y + Error Boundary + IPC validation + schema migrations
Second pass through the audit punch-list. ESLint and Prettier now clean
(0 errors, 0 warnings), typecheck clean, 53 tests pass.
ACCESSIBILITY (Modal)
- Full focus trap: Tab/Shift-Tab cycle within the dialog and never
escape to the underlying page.
- Focus restoration: closing returns focus to the trigger button.
- First focusable child is focused on open (skipping the X button).
- aria-labelledby links the dialog to its <h2> via useId().
- Close button's hardcoded "Закрыть" replaced with i18n key.
ERROR RECOVERY
- Add ErrorBoundary component (class — only way) with localized
fallback and a "try again" reset button. Stack trace shown only in
dev. Wrapped around the whole App + a nested boundary around the
routed pages so a crash in one route doesn't blank the chrome.
- Module-level guard on subscribeToBackend so React 18 StrictMode's
dev-mode double-mount doesn't subscribe twice.
- Loading placeholder is now blank (was hardcoded Russian "Загрузка…"
that English users would see during initial hydration).
TRAY i18n
- 5 tray strings now follow the current settings.language. Falls back
to Russian when the store isn't loaded yet or the lang is unknown.
- refreshMenu() called on settings.language change and on
pauseAll/resumeAll so the pause label stays in sync with state.
IPC VALIDATION (src/main/validate.ts)
- Hand-rolled validators for every renderer-supplied payload:
exercise input/patch, challenge input/patch, settings patch, id,
actualReps, snoozeMinutes. Range-check numeric fields
(intervalMinutes ∈ [1, 1440], reps ∈ [1, 9999], multiplier ∈ [0,
1000], snooze ∈ [1, 1440]), cap string lengths at 200, restrict
enums (theme/lang/notify-mode/stat) to known values, validate
quietHours.from/to with HH:MM regex and dedup quietHours.days.
- Every ipcMain.handle for mutations now runs the validator first and
returns null on rejection instead of pushing junk into the store.
A compromised renderer can no longer corrupt persisted state via
out-of-range numbers or wrong-type fields.
SCHEMA MIGRATIONS (src/main/store.ts)
- Add __schemaVersion field to persisted state with CURRENT = 1.
- MIGRATIONS map: { 0: (s) => s } as a no-op seed; future structural
changes (e.g. quietHours shape revision) get a single explicit slot.
- runMigrations() applies migrations in order; coerce() normalises the
result into a fully-formed AppState. Both first-write and every
flush() persist the version field.
EXHAUSTIVE-DEPS WARNINGS
- Dashboard: memoise `exercises` so downstream useMemos don't fire on
every parent render; gate the history fetch on exercises change
instead of any state change.
- HistoryHeatmap: wrap `weeks` in useMemo so monthLabels' deps are
stable.
LINT POLISH
- updater.ts: refactor a Prettier-vs-no-extra-semi conflict by
extracting the cast into a local binding.
- Remove dead import of `Challenge` from ipc.ts (now imported via
validators).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
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>
|
||
|
|
d6f94ee1c9 |
docs+chore: retry upload on TLS/504 + refresh README/RELEASING
Upload script: - Retry curl on transient network failures (504, schannel TLS abrupt close): up to 4 retries with 15s/45s/2m/5m backoff. Before each retry, list the release assets server-side — Gitea sometimes commits the body but times out the response, so the file may already be there at the expected size (skip retry). If present at wrong size (partial), delete before re-uploading. ASCII-only (PS5.1 reads files in CP1251 without BOM). Docs: - README: bump release/test badges to v0.5.1 / 51 tests; mention silent retry in the auto-update feature line. - RELEASING: rewrite around the new update-channel architecture, bridge tags, and dropped Gitea Actions workflows. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
6160ece8d4 |
fix(release): retry uploads with backoff + drop Gitea workflows
Gitea/nginx intermittently returns 504 on large multipart uploads even when curl successfully streamed the body. Add up to 4 retries with exponential backoff (15s/45s/2m/5m). Before each retry, check whether the asset is actually present server-side at the expected size — Gitea sometimes accepts the body but times out the response, so the file is already there. Also drop .gitea/workflows/* — we use release.ps1 locally and Gitea Actions runners are not configured, so every push was leaving queued/ failed workflow runs in the Actions tab. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
3f038e59e8 |
chore(release): v0.5.1
|
||
|
|
33e237948e |
fix(release): write package.json as UTF-8 without BOM
PS5.1 Get-Content -Raw without -Encoding utf8 reads in CP1251, mangling non-ASCII like em-dash. Set-Content -Encoding utf8 writes a BOM that breaks PostCSS / electron-builder reads of package.json. Use .NET ReadAllText/WriteAllText with UTF8Encoding(false) to guarantee roundtrip-safe UTF-8 without BOM. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
f861af5db1 |
feat(updater): fixed-URL auto-update channel + silent retries
The auto-update system used a per-version publish URL
(releases/download/v${version}), so each installed build only ever
checked its own release page for new versions. To deliver an update we
had to manually copy the new manifest into every old release — easy to
forget, and any half-uploaded state showed users red "check failed"
banners.
Architectural fix:
- New rolling 'update-channel' Gitea release. publish.url is now a
fixed path (.../releases/download/update-channel) that never moves.
- release.ps1 uploads each new build to three places:
1. vX.Y.Z (historical archive + changelog)
2. update-channel (what every client polls)
3. -BridgeTags (transition: also fill in old releases so users
still on those versions can find the new build)
- upload-release-assets.ps1 gains -AssetVersion to upload version-X.Y.Z
artifacts into a non-version tag (channel/bridge).
Resilience fixes for the updater itself:
- Hourly checks and the boot check now run in SILENT mode: network
errors don't promote to a red error state, they're logged and
retried on the next tick. Only user-initiated "Check now" surfaces
errors. This prevents the cascade of "Ошибка проверки" cards on
flaky networks or partial uploads.
- Boot check retries up to 3 times (30s/2m/5m backoff) before giving
up until the hourly tick.
- Track lastCheckedAt; "Up to date" subtitle now shows "checked Nm ago".
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
c9d4fc237e |
feat(v0.5.0): history + streak + heatmap, quiet hours, partial reps, README
== История и стрики (#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>v0.5.0 update-channel |
||
|
|
973339ca62 |
feat(i18n): bilingual UI (Russian + English) + language selector
Все UI-строки приложения переведены и переключаются на лету через
Settings → Язык интерфейса.
== i18n архитектура ==
- src/renderer/src/i18n/dict.ts — плоский словарь ru/en с ~190 ключами,
поддержка интерполяции {var} и плюрализации
- src/renderer/src/i18n/index.ts — useT() React hook + чистые
translate/translateN функции (для ReminderApp вне store context)
- Settings.language: 'ru' | 'en', default 'ru'
- Изменение языка применяется немедленно через Zustand reactive update
== Что переведено ==
- Sidebar nav + slogan + status
- Titlebar window controls (aria-labels)
- Dashboard: hero, 3 stat-карточки (Активных / До следующего /
Трекинг матчей), Paused banner, empty state
- Exercises: hero, секции (активные / выключенные), row meta, empty
- Challenges: hero, formula subtitle, warning, row format
«{stat} × {mult} → {exercise}», empty
- Games: hero, status badges (Live/Ready/Queued/Installed/Not found),
queued/no_user banners, dev panel
- Settings: все секции + новый Language selector
- UpdaterCard: все состояния (checking/available/downloading/
downloaded/error/idle) с интерполяцией версии и MB/s
- ReminderApp: kicker «Время тренировки», reps подпись, snooze label
с динамическими минутами, кнопки done/skip
- Match summary: победа/поражение, плюрализация «N челлендж/-а/-ей»
vs «N challenge/-s»
- Format helpers (formatCountdown, formatInterval) — теперь принимают
Language параметр
== Локалезависимая дата ==
Dashboard hero показывает today в текущей локали:
ru-RU → "воскресенье, 17 мая"
en-US → "Sunday, May 17"
== STAT_LABELS bilingual ==
- shared/types.ts: STAT_LABELS_EN + statLabel(stat, lang) helper
- ChallengeResult получил поле stat?: GameStat (для resolve на стороне
renderer'а с актуальным языком, вместо baked-in label)
- main/games/registry.ts кладёт stat в результат
== Тесты ==
- src/renderer/src/i18n/i18n.test.ts: 10 кейсов
* translate: lookup, fallback, interpolation, multi-var, lang fallback
* translateN: ru plural rules (1/21/101 → one; 2-4 → few; 0/5-20 → many)
и en (1 → one, else → many)
- Всего 33 теста зелёные
== Известное ограничение ==
SAMPLE_EXERCISES (5-6 русских "Приседания / Отжимания / ...") остаются
русскими — это seed данных на первый запуск. Английский юзер сразу
переключит язык и сможет переименовать вручную. Делать seed-per-locale
оверкилл — слишком много кода ради малого.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
v0.4.0
|
||
|
|
70eb4717ec |
release(v0.3.7): auto-check каждый час вместо 6
6 часов было выбрано произвольно как "вежливо для сервера". На практике слишком долго для backgound-приложения: новый релиз доезжает до пользователя только через полдня. Меняем на 1 час — все сравнимые приложения (Discord 30 мин, Slack 30 мин, VS Code 1 ч) используют похожие интервалы. Стартовая проверка (5 сек после запуска) остаётся. Нагрузка минимальна: запрос на latest.yml = 362 байта. UI текст «Авто-проверка раз в 6 часов» → «раз в час». Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>v0.3.7 |
||
|
|
f141d6f0d8 |
chore(updater): remove token-bake — repo is public now
После того как репозиторий стал публичным, токен в Authorization
header больше не нужен. Убираю __UPDATE_TOKEN__ define из
electron.vite.config.ts и весь связанный код в updater.ts.
Преимущества:
- Никаких секретов в распространяемом .exe
- Билд не требует UPDATE_TOKEN env переменной
- Любой может склонировать и собрать без доп. конфига
== Действие пользователя ==
Если v0.3.5 .exe был скачан кем-то с публичного репо (был доступен
~30 минут до того как мы это поняли) — токен из него можно
извлечь и использовать для записи в репо. Рекомендую ротировать:
1. Gitea → Settings → Applications → удалить старый токен
2. Создать новый, скопировать
3. PowerShell:
[Environment]::SetEnvironmentVariable('GITEA_TOKEN', '<новый>', 'User')
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
v0.3.6
|
||
|
|
90f2ebeea1 |
chore: remove .claude/ workspace folder from tracking
Эта папка содержит Claude Code workspace-конфиг (skills, agent definitions, локальные permissions). Уже в .gitignore, но была залита в Initial commit до того как gitignore обновился. Перед публикацией репозитория убираем из текущего HEAD. Файлы остаются на локальном диске (только --cached). История по-прежнему содержит .claude/ — это не секреты (Anthropic skills публичны, settings.local.json содержит только permission allowlist), но для полной чистки можно переписать историю через git filter-repo. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
43ebc8d74c |
fix(updater): запекаем токен в build для приватного Gitea repo
Корень проблемы 404: репо приватный, Gitea требует Authorization
header для release assets даже на browser_download_url. Без токена
запрос возвращает 404 (не 401), поэтому electron-updater сообщал
"Cannot find channel latest.yml update info".
Решение — embed read-only токена в build:
- electron.vite.config.ts: vite `define` для __UPDATE_TOKEN__
читает process.env.UPDATE_TOKEN на этапе сборки
- src/main/updater.ts: если __UPDATE_TOKEN__ непустой, выставляет
autoUpdater.requestHeaders = { Authorization: 'token ...' }
- Декларация declare const локально в модуле, vite заменяет литерал
Сборка теперь требует:
$env:UPDATE_TOKEN = '<gitea-token>'; npm run dist
Если переменная не задана — токен пустой, auto-update тихо отключается
(статус 'unsupported' не показывается, просто запросы будут падать).
Альтернатива на будущее (без токена в .exe):
1. Сделать репо публичным в Gitea Settings
2. Или сделать только Releases публичными если в этой версии Gitea
есть такая опция
Тогда токен в коде не нужен.
== Важно для существующих пользователей ==
Установленные v0.2.x / v0.3.0-0.3.4 не могут получить апдейт
автоматически — у них в бинаре старый updater без токена и они
продолжат получать 404. Им нужно скачать v0.3.5 .exe вручную
и переустановить. После 0.3.5 auto-update заработает.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
v0.3.5
|
||
|
|
ee2dc19daa |
release(v0.3.4): шрифты — Plus Jakarta Sans + Bricolage Grotesque
Manrope воспринимался слишком строгим и корпоративным. Замена даёт больше характера и тёплый "попсовый" feel: - Body/UI: Manrope → Plus Jakarta Sans (мягкие округлые формы 'a' 'g', очень распространён в современных трендовых приложениях) - Display/hero: Fraunces → Bricolage Grotesque (variable шрифт с opsz axis: 24 для нормальных заголовков, 96 для hero — гротеск с характерными слегка сжатыми формами и большим контрастом штрихов) - Mono: JetBrains Mono без изменений Все hero-заголовки пробампаны до 34→40px и font-bold (700), Bricolage лучше всего смотрится в полужирном/жирном. Sidebar логотип «Laude» тоже font-bold. Также: - body line-height: 1.45 → 1.5 для лучшей читаемости - Reminder exercise name: 28→30, semibold→bold - Match summary title: 24→26, semibold→bold - Sidebar slogan: 12→13/medium, контраст 45→55 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>v0.3.4 |
||
|
|
9b488164e0 |
release(v0.3.3): акцентная типография — все надписи крупнее и контрастнее
Жалоба: вторичные подписи (Активных / До следующего / Трекинг матчей / Возобнови чтобы продолжить отсчёт) выглядели мелко и плохо читались. Сделан sweep по всему UI: - Базовая шкала secondary text: 12px → 13-14px - Контрастность подписей: text-text/45 → text-text/65 (или /75 для лейблов) - font-medium → font-semibold для метаданных карточек Dashboard: - Дата: 13/font-medium → 14/font-semibold - HeroStat label: 12/medium/55 → 14/semibold/75 (вот эти "Активных" и пр.) - HeroStat value: 26/semibold → 28/bold - HeroStat subvalue: 12/45 → 13/60/medium - HeroStat icon plaque: 24px → 28px - Paused banner title: 14 → 16, hint: 12 → 14/70 - Иконка баннера 36→40px Settings: - ToggleRow/SelectRow label: medium → semibold - Hint: 12/55 → 13/65/medium - "Конфигурация": 13/45/medium → 14/65/semibold Exercises/Challenges (row + page): - Row title: 15/medium → 16/semibold - Row subtitle: 13/55 → 14/65/medium - Стат-метрики bold - Empty state: 14/55 → 15/65/medium - Warning banner: 13/80 → 14/85/medium + иконка крупнее Games: - Game title: 17/semibold → 18/bold - Install path subtitle: 12/45 → 13/55/medium - Queue/error banners: 13/80 → 14/85/medium + крупнее иконки и code ExerciseCard: - Title: 17/semibold → 18/bold - Reps meta: 13/55 → 14/65/medium - Countdown label "Через/Сейчас": 11/45 → 12/60/semibold - Countdown value: 22/semibold → 24/bold - "Готово" CTA: 14/semibold → 15/bold, h-10 → h-11 ReminderApp: - "Время тренировки" label: 12/45 → 13 + accent цвет + bold - "Раз" подпись: 14/55 → 15/65/semibold - "Следующее через": 12/45 → 13/65/medium - Match summary header: 11/45 → 12/65/semibold - Match summary subtitle: 12/45 → 13/65/medium - Match summary total: 12/55 → 13/65/medium + 14 → 16 для числа - ChallengeRow title: 14/medium → 15/semibold - ChallengeRow subtitle: 12/55 → 13/65/medium - CTA Готово: 15/semibold → 16/bold UpdaterCard: - Cell title medium → semibold - Cell subtitle: 12/55 → 13/65/medium - Иконка ячеек 36→40px - Progress download title medium → semibold + 18px процент Card SectionHeader: - 12/medium/45 → 13/semibold/60 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>v0.3.3 |
||
|
|
aa60acb164 |
release(v0.3.2): новые шрифты, иконки сайдбара, light по умолчанию
== Шрифты == - Sans/display: Geist → Manrope (мягче, дружелюбнее, ближе к SF Rounded) - Serif (hero titles): Instrument Serif → Fraunces с opsz axis 144 - Mono: Geist Mono → JetBrains Mono с ss02/ss19/zero features == Размеры (iOS HIG calibration) == - Hero h1: 40-44px → 32-36px (ближе к настоящему iOS Large Title 34pt) - Reminder name: 32 → 28; reps counter: 64 → 56 - Match summary title: 22 → 24 - Dashboard stat value: 30 → 26 - Body line-height/letter-spacing подкручены под Manrope == Иконки сайдбара == - LayoutDashboard → Sun (Сегодня — утренняя энергия) - ListChecks → Dumbbell (Упражнения — спорт прямо) - Gamepad2 → Joystick (Игры — более игровая иконка) - Target → Flame (Челленджи — интенсивность) - Settings → Settings2 (немного объёмнее) - Размер плашки 28→32px, иконки 15→17px == Light theme == - DEFAULT_SETTINGS.theme: 'system' → 'light' (новые установки сразу получают светлую тему) - Light bg прогрет: 242,242,247 → 245,245,249 - surface-2 чуть темнее для лучшей сепарации полей ввода - text не pure black а 17,17,19 (легче глазам) - shadow-card получила тёплый slate-tinge Существующие пользователи с theme='system' продолжат следовать ОС. Для принудительного переключения — Settings → Тема → Светлая. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>v0.3.2 |
||
|
|
660b6d57d8 |
chore(release): v0.3.1 — Apple/iOS дизайн
Включает полный реворк UI в стиле Apple iOS/macOS: - Geist + Instrument Serif шрифты вместо Rajdhani - Apple HIG палитра (systemOrange, systemGreen, systemRed, true black dark) - macOS vibrancy sidebar, iOS grouped lists, UISwitch, action sheets - Spring анимации, active:scale press feedback Установщик ведёт себя как install-or-update — обновляет существующую 0.2.x/0.3.0 копию с сохранением настроек. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>v0.3.1 |
||
|
|
c5a29214d2 |
redesign(ui): полный реворк в стиле Apple iOS/macOS
Ветка для нового интерфейса; main с предыдущим Strava-дизайном
не тронут — можно вернуться через checkout main.
== Design system (foundation) ==
- Шрифты: Geist (sans, display) + Geist Mono (HUD числа) +
Instrument Serif (hero titles) — все через Google Fonts. Близки
к SF Pro / SF Mono.
- Палитра: Apple Human Interface Guidelines
* accent: 255 107 53 (Apple Fitness Move orange)
* success: 52 199 89 (systemGreen — для switch)
* destructive: 255 59 48 (systemRed)
* info: 0 122 255 (systemBlue)
* warning: 255 159 10
* Light bg: 242 242 247 (iOS systemGroupedBackground)
* Dark bg: true black 0 0 0 (OLED-friendly), elevation через
28/44/56 grey steps как в iOS Settings
- Утилиты: .vibrancy (macOS Big Sur sidebar), .hairline-b/-t (0.5px
iOS-стиль), .shadow-card (soft layered), .font-display/-serif/-mono-num
== UI primitives ==
- Button: filled / tinted / plain / destructive / success (iOS UIButton);
active:scale-[0.97] press feedback. Старые имена primary/secondary/
ghost/danger/victory маппятся через legacyMap для совместимости.
- Switch: настоящий iOS UISwitch 51x31, spring физика knob, success
цвет on.
- Modal: центрированный sheet с rounded-3xl (22px), backdrop blur,
spring scale-in. Header с font-display, X в circle.
- Card + Row + SectionHeader: iOS grouped list — белая поверхность,
hairline-b dividers между rows, last={true} убирает последний.
== App frame ==
- Sidebar: vibrancy (semi-transparent + backdrop-blur saturate 180%),
font-serif лого, tinted icon-plaques на каждом пункте (как в iOS
Settings), плавный hover. Drawer на mobile со spring slide.
- Titlebar: центрированный title, window controls без glow, hamburger
только на <md.
- App.tsx: AnimatePresence cross-fade между маршрутами.
== Pages ==
- Dashboard: hero с font-serif Large Title + датой. 3-card Hero panel
(Apple Fitness style) с tinted icon squares. ExerciseCard теперь с
progress-ring вокруг иконки + появляющейся "Готово" pill только при
due. Three-dot menu (iOS-style popover).
- Exercises: групированный список iOS, разделение Активные/Выключенные,
chevron-right на каждом row.
- Challenges: тот же групированный паттерн + warning banner если игр
нет, formula preview карточка в редакторе с big number в accent.
- Games: cards в новом стиле, статус-чипы pulse-dot для LIVE, dev
кнопки в pill-стиле.
- Settings: классические iOS Settings секции с ToggleRow и SelectRow
inside Card. UpdaterCard полностью переработан под Cell pattern.
== Reminder window ==
- iOS action sheet: большая иконка в accent-circle сверху, font-serif
название упражнения, гигантское моноширинное число reps. Кнопки
стопкой: primary Готово full-width, потом snooze + skip в grid.
Хоткеи Enter/Space/Esc сохранены.
- Match summary: tone-цветной icon plaque (success/destructive/accent),
ChallengeRow с pill-shaped check button.
== Анимации ==
- Spring физика везде где layout (Switch knob, Modal, Sidebar drawer,
карточки)
- active:scale-[0.97] на всех интерактивных элементах (iOS touch feel)
- Cross-fade между страницами через AnimatePresence
- Никаких glow / pulse-ring — apple style это сдержанность
Verified: typecheck OK, 23 tests pass, build 36.35 KB CSS (на 6 KB
меньше предыдущего HUD-стиля), 1.56 MB JS.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
6ffa100645 |
feat(release): add upload-release-assets.ps1 helper
Скрипт догружает уже собранные NSIS-артефакты (.exe, .blockmap, latest.yml) в существующий Gitea release. Создаёт release если его нет, удаляет одноимённые активы перед перезаписью. Используется когда: - release.ps1 успел запушить тег, но упал на загрузке - релиз делался руками без артефактов - нужно перезалить артефакты после пересборки Большие файлы (>50MB) грузятся через curl.exe, потому что Invoke-RestMethod в PS 5.1 разрывает соединение на multipart upload 80+MB. Прогресс печатается отдельно для каждого файла. Использован для загрузки v0.3.0 артефактов. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
e95a8bacb6 |
chore(release): v0.3.0
Релиз новой спортивной палитры и адаптивной вёрстки. - Strava orange + rose цветовая гамма - Полная адаптивность: collapsible sidebar, drawer на mobile - 23 unit-теста, typecheck чистый - Установщик Exercise-Reminder-Setup-0.3.0.exe собран Установщик ведёт себя как install-or-update: ставится на чистую систему, обновляет существующую 0.2.x с сохранением настроек. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>v0.3.0 |
||
|
|
cec146ae3a |
redesign(ui): спортивная палитра Strava + полная адаптивность
== Цветовая гамма == Сменили esports cyan+violet на спортивную Strava-палитру: - accent: orange-500 (#F97316) — энергия, движение, "GO" - accent-2: rose-500 (#F43F5E) — интенсивность, gradient pair - victory: lime-500 — личный рекорд / готово - defeat: red-600 - xp: amber-500 Dark theme переведена с холодного navy на тёплый графит, light theme очищена в "athletic paper" feel. == Репозиционирование текста == Убрали esports-сленг в пользу спортивной лексики: - Sidebar: "Play hard · Train harder" → "Move every day" - Sidebar footer: "GSI-трекинг матчей" → "Трекинг активности" - Dashboard микрозаголовок: "Mission control" → "Тренировка дня" - Dashboard HUD: "Cooldown / READY" → "До следующего / СЕЙЧАС", "Game tracking / LIVE / OFF" → "Трекинг матчей / LIVE / OFF" - Exercises: "Loadout" → "Программа" - Games: "Game integrations" → "Трекинг матчей" - Challenges: "Match rules" → "Правила за матч" - Settings: "Config" → "Конфигурация" - ExerciseCard: "Cooldown / PAUSED" → "Через / пауза" - ReminderApp: "Cooldown ready" → "Время тренировки", "Время размяться" → "Двигайся", "Next drop" → "Следующее", "Victory · упражнения заработаны" → "Победа · тренировка заработана", "Defeat · но тело — нет" → "Проигрыш · но тело не сдаётся", "all clear" → "готово", "Total / reps" → "Всего / повторов", "GG" → "Готово" == Адаптивная вёрстка == - App.tsx: state mobileNavOpen, авто-закрытие drawer на route change - Sidebar: три режима через CSS breakpoints — * lg+ (≥1024px): полная ширина w-60 с лейблами * md (768-1023px): icon-only w-16 с title-тултипами * <md (<768px): скрыта, открывается drawer-ом по hamburger - Titlebar: hamburger button слева на <md, title скрывается на <sm - Все hero-блоки страниц: flex-col на sm, flex-row sm:items-end sm:justify-between с stack gap-4 - Padding страниц: p-4 sm:p-6 lg:p-8 вместо p-8 - Hero h1: text-3xl sm:text-4xl - Dashboard HUD strip: grid-cols-2 lg:grid-cols-4 (было 1/2/4) - Action buttons в карточках/строках: opacity-100 lg:opacity-0 lg:group-hover (на узких экранах всегда видны) - GameRow buttons: flex-wrap для длинных лейблов - Dashboard challenges shortcut: hint hidden sm:block - Sidebar mobile drawer: framer-motion слайд + backdrop с blur Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
92e15e69a3 |
feat: auto-update, тесты и CI/CD
Полная автоматизация релизного цикла. == 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> |
||
|
|
757352e447 |
chore(deps): add electron-updater + vitest
Подготовка к auto-update и тестам. - electron-updater для in-app апдейтов через generic provider - vitest для unit-тестов Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
ea58daa693 |
chore(release): bump version 0.1.2 → 0.2.0 для редизайн-релиза
Маркируем новый билд установщика после полного UI-редизайна (phase 1 + phase 2 esports HUD). Установщик Exercise-Reminder-Setup-0.2.0.exe уже корректно ведёт себя как install-or-update: - appId com.anril.exercise-reminder неизменен → NSIS находит предыдущую инсталляцию 0.1.x и обновляет её - deleteAppDataOnUninstall=false → настройки и история юзера сохраняются при апдейте - perMachine=false → апдейт без прав админа - differentialPackage=true → готовность к дифф-апдейтам через electron-updater (если позже подключим) Сам .exe не коммитится — release/ в .gitignore (бинарь 85 МБ). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
d418b9bbd2 |
redesign(ui): phase 2 — все вторичные страницы и UI-примитивы
Распространили esports-HUD язык на оставшиеся экраны и общие UI:
- Modal: neon border accent/25 с двойным glow (cyan + violet),
gradient-divider, gradient-stripe слева от заголовка, defeat hover
на закрытие
- Switch: bg-gradient-brand с shadow-glow в on-состоянии
- Exercises: hero с gradient-text, статистика в моноширинном шрифте
(active count, total reps за цикл), HUD-список с анимированным
появлением, hover-row с accent tint
- Games: game cards с gradient orb-иконкой + glow на интегрированных,
shadow-glow-victory + анимированная LIVE точка для активной
интеграции, новые status badges (LIVE / READY / QUEUED / INSTALLED /
NOT FOUND) в display-шрифте, ошибки в защитных цветах (xp/defeat)
- Challenges: hero + formula-row "stat × N → reps" в моноширинном
шрифте, gradient-preview карточка в редакторе с большим итоговым
числом в text-gradient-brand
- Settings: hero, секции с иконкой в accent-плашке, заголовки секций
в display-шрифте uppercase tracking-wide
- ExerciseEditor: preview-карточка с gradient-orb иконкой в шапке
модала, моноширинные input для чисел, scale-up на выбранной иконке
Все правки используют существующие токены из phase 1 — никаких новых
CSS-переменных или конфигов.
Откат phase 2 один: git revert HEAD
Откат до начала редизайна: git reset --hard
|
||
|
|
4da83761d2 |
redesign(ui): phase 1 — esports HUD design system + core surfaces
Сменили визуальную ДНК на dark-first гейминг-эстетику: cyan→violet
неоновая палитра, спортивный display-шрифт Rajdhani, моноширинный
HUD для всех счётчиков, градиентные CTA с glow.
Дизайн-система (globals.css + tailwind.config.js):
- Brand-токены accent (cyan), accent-2 (violet), victory (lime),
defeat (rose), xp (amber); --bg-deep слой
- Утилиты .neon-border (анимированный обводный градиент),
.hud-pulse, .hud-scanlines, .dot-grid, .text-gradient-brand,
.bg-gradient-brand/-victory/-defeat
- Радиальные градиенты на body (cyan/violet glow по углам)
- Шрифты Rajdhani (display) и JetBrains Mono подключены через CDN
Компоненты:
- Sidebar: gradient-логотип, активный пункт с вертикальной gradient-
полосой и shadow-glow, статус-чип GSI tracking
- Titlebar: glass + scanlines, моноширинный лейбл, defeat hover на X
- Button: primary = bg-gradient-brand + shadow-glow; новый variant
victory (lime-gradient)
- ExerciseCard: SVG cooldown-ring как у способностей в MOBA,
градиентный stroke с drop-shadow, .neon-border на due, hover lift
- Dashboard: hero с gradient-text заголовком, HUD-полоса из 4 stat-
карточек (Cooldown / Active / Avg / Game tracking LIVE/OFF)
- ReminderApp: вращающийся conic-gradient вокруг иконки, HUD-блок
reps в моноширинном шрифте, кнопки с подписями хоткеев,
Match summary с heroGradient по результату (victory/defeat/brand)
ThemeProvider больше не перезаписывает --accent системным —
у laude теперь стабильная brand-идентичность.
Бонус: хоткеи на reminder-окне (Enter=done, Space=snooze, Esc=skip).
Откат: git revert HEAD или git reset --hard
|
||
|
|
688a86b611 | Initial commit |