Add motivational training challenges and shared club goals

This commit is contained in:
larssand
2026-08-30 09:16:11 +02:00
parent 27dcbd92aa
commit 694aba5f3d
11 changed files with 799 additions and 13 deletions

View File

@@ -476,11 +476,14 @@ Owns:
- daily summaries, controlled-lap streak, and recent personal-best gain
- signed personal pace and consistency trends from the latest 30 laps versus the preceding 30, with a minimum of 10 laps in each window
- derived automatic achievements for sessions, lap totals, training days, controlled streaks, and a completed five-minute result
- derived optional daily missions for lap volume, controlled consecutive laps, and a completed five-minute stint
- derived positive highlights for current activity, personal-best gain, pace, consistency, and endurance
- active 30-minute pace buckets using a trimmed average and omitting empty clock periods
- persisted class-specific setup markers and 30-minute before/after comparisons
- extended stint analysis with normal pace, best 10-lap race pace, mistake count, consistency, and start/end change
- rule-based practice recommendation selection
- automatic and custom milestone progress calculation
- shared club-goal progress from a persisted ledger baseline
Rules:
- only passings with a matching `driverId` are included
@@ -495,6 +498,8 @@ Rules:
- consistency trend is `previous spread - current spread`, so a positive value means a smaller recent spread
- trend fields remain neutral until both comparison windows contain at least 10 valid laps
- achievements are derived on read and are not persisted; custom reward goals remain persisted in `state.training.goals`
- daily missions and positive highlights are derived on read, reset by local calendar day, and remain isolated to the selected class
- the shared club goal is persisted, but its progress is derived from `ledger.length - clubGoal.baselineLaps`
- every save replaces ledger rows only for session IDs that still exist in `resultsBySession`
- ledger rows for a deleted session are retained and become historical records
- `sourceKey` deduplicates repeated saves of the same session passing
@@ -519,13 +524,16 @@ Owns:
- mobile-responsive training markup
- compact live-practice card and primary personal-profile action for the selected driver
- personal trend and achievement cards that never combine classes
- positive-credit and optional daily-challenge cards based on the selected driver's own development
- recent club progress feed ordered by latest activity, without position numbers or cross-driver performance scoring
- shared club-goal create, celebrate, replace, and delete actions in the overview
- collapsed Extra statistics with pace heatmap, setup-marker actions, weekday pace, featured stints, and an expandable full stint table
Mutations:
- replaces or appends entries in `state.training.goals`
- creates, rotates, enables, or disables entries in `state.training.shares`
- creates and removes entries in `state.training.markers`
- replaces or clears `state.training.clubGoal`
- always calls injected `saveState()` before rerender
Route behavior:
@@ -1767,7 +1775,8 @@ This bucket is the most dynamic part of state and is mutated heavily during live
goals: TrainingGoal[],
ledger: TrainingLap[],
shares: TrainingShare[],
markers: TrainingMarker[]
markers: TrainingMarker[],
clubGoal: TrainingClubGoal | null
}
```
@@ -1797,6 +1806,22 @@ This bucket is the most dynamic part of state and is mutated heavily during live
}
```
`TrainingClubGoal`:
```js
{
id: string,
title: string,
reward: string,
targetLaps: number,
baselineLaps: number, // ledger length when this club challenge starts
createdAt: string,
celebratedAt: string // empty until the reached goal is acknowledged
}
```
Club-goal progress is intentionally aggregate-only. Creating a replacement goal captures the current ledger length as a new baseline; historical laps remain available for personal statistics but do not advance the new shared target.
Share rules:
- no share exists until an admin explicitly enables the selected personal profile
- only one normalized share entry is retained per personal profile
@@ -1856,6 +1881,7 @@ Migration behavior:
- the next `saveState()` imports valid registered-driver laps from all current session results
- invalid laps, anonymous transponders, and laps at or below `500 ms` are excluded
- older state without `training.markers` normalizes to an empty marker list
- older state without `training.clubGoal` normalizes to `null`
## 15.9 `Settings`