fix(P0): match-history, tray/dashboard pause sync, whatsnew для upgraders

P0 #1 — Match-челленджи теперь пишутся в историю.
    HistoryEntry расширен полями `reps?`, `name?`, `source?` (snapshot
    planned-reps + name на момент записи + 'reminder'/'match').
    Новый store.markChallengeDone(challengeId, reps) пишет entry с
    exerciseId='challenge:<id>' и source='match'.
    Зарегистрирован IPC.markChallengeDone handler (раньше канал был в
    enum, но handler не подключен).
    ReminderApp.MatchSummaryView вызывает window.api.markChallengeDone
    при ✓-клике. Стрик, today_done, achievements теперь учитывают
    игровые тренировки.
    Заодно dailyReps/dailyRepsRange/totalDoneReps используют
    entry.reps как fallback — heatmap не теряет данные после удаления
    упражнения (закрывает P2 #12).

P0 #2 — Tray-пауза синхронизирована с Dashboard.
    Раньше tray держал scheduler-local `paused` boolean, который не
    отражался в settings.globalEnabled — Dashboard показывал «running»
    с тикающим таймером, хотя fires не приходили. Сейчас оба пути
    (tray и Dashboard-кнопка) меняют единственный source of truth —
    settings.globalEnabled. setPaused/isPaused/paused удалены, IPC
    pauseAll/resumeAll переписаны на updateSettings.

P0 #3 — Whats-new покажется существующим пользователям при апгрейде.
    Раньше для всех undefined lastSeenVersion (включая обновляющихся
    с v0.5.5) делали silent-save без модалки — никто бы не увидел
    v0.5.6 changelog. Сейчас: если есть Exercise с lastDoneAt → это
    обновляющийся пользователь, показываем заметки текущей версии;
    если нет — новичок, silent.
This commit is contained in:
AnRil
2026-05-22 14:49:29 +07:00
parent 77007636df
commit 17df87b3aa
10 changed files with 138 additions and 43 deletions

View File

@@ -1,9 +1,9 @@
import { Tray, Menu, nativeImage, app } from 'electron'
import { join } from 'node:path'
import { showMainWindow } from './windows'
import { isPaused, setPaused, forceCheck } from './scheduler'
import { snoozeAll } from './state-actions'
import { getSettings } from './store'
import { forceCheck } from './scheduler'
import { broadcastState, snoozeAll } from './state-actions'
import { getSettings, updateSettings } from './store'
import type { Language } from '@shared/types'
let tray: Tray | null = null
@@ -69,16 +69,21 @@ export function createTray(): Tray {
export function refreshMenu(): void {
if (!tray) return
const paused = isPaused()
// Single source of truth — settings.globalEnabled. Раньше tray держал
// отдельный scheduler-local `paused` flag, который не синхронизировался
// с Dashboard'ом (там кнопка читает globalEnabled). Теперь оба пути
// правят одно поле.
const paused = !getSettings().globalEnabled
const menu = Menu.buildFromTemplate([
{ label: trayLabel('open'), click: () => showMainWindow() },
{ type: 'separator' },
{
label: paused ? trayLabel('resume') : trayLabel('pause'),
click: () => {
setPaused(!paused)
updateSettings({ globalEnabled: paused }) // toggle
broadcastState() // чтобы Dashboard перерисовал кнопку сразу
refreshMenu()
if (!paused) forceCheck()
if (paused) forceCheck() // resuming — догнать пропущенные fires
}
},
{