Commit Graph

17 Commits

Author SHA1 Message Date
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
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>
2026-05-19 13:23:41 +07:00
AnRil
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>
2026-05-18 23:21:27 +07:00
AnRil
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>
2026-05-18 23:04:49 +07:00
AnRil
f861af5db1 feat(updater): fixed-URL auto-update channel + silent retries
Some checks failed
CI / Typecheck + Tests (push) Has been cancelled
CI / Build (Windows) (push) Has been cancelled
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>
2026-05-18 15:23:41 +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
973339ca62 feat(i18n): bilingual UI (Russian + English) + language selector
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
Все 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>
2026-05-17 23:28:34 +07:00
AnRil
70eb4717ec release(v0.3.7): auto-check каждый час вместо 6
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
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>
2026-05-17 19:01:06 +07:00
AnRil
ee2dc19daa release(v0.3.4): шрифты — Plus Jakarta Sans + Bricolage Grotesque
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
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>
2026-05-17 18:36:59 +07:00
AnRil
9b488164e0 release(v0.3.3): акцентная типография — все надписи крупнее и контрастнее
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
Жалоба: вторичные подписи (Активных / До следующего / Трекинг матчей /
Возобнови чтобы продолжить отсчёт) выглядели мелко и плохо читались.

Сделан 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>
2026-05-17 18:00:52 +07:00
AnRil
aa60acb164 release(v0.3.2): новые шрифты, иконки сайдбара, light по умолчанию
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
== Шрифты ==
- 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>
2026-05-17 17:49:46 +07:00
AnRil
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>
2026-05-17 14:17:35 +07:00
AnRil
cec146ae3a redesign(ui): спортивная палитра Strava + полная адаптивность
Some checks failed
CI / Typecheck + Tests (push) Has been cancelled
CI / Build (Windows) (push) Has been cancelled
== Цветовая гамма ==
Сменили 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>
2026-05-17 12:50:20 +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
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 688a86b

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 18:42:05 +07:00
AnRil
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 688a86b

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